chef-attribute-validator 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (52) hide show
  1. checksums.yaml +15 -0
  2. data/.gitignore +20 -0
  3. data/CHANGES +4 -0
  4. data/Gemfile +2 -0
  5. data/LICENSE +27 -0
  6. data/README.md +109 -0
  7. data/Rakefile +27 -0
  8. data/chef-attribute-validator.gemspec +25 -0
  9. data/lib/chef-attribute-validator.rb +52 -0
  10. data/lib/chef-attribute-validator/attribute_set.rb +90 -0
  11. data/lib/chef-attribute-validator/check.rb +56 -0
  12. data/lib/chef-attribute-validator/checks/looks_like.rb +52 -0
  13. data/lib/chef-attribute-validator/checks/max_children.rb +37 -0
  14. data/lib/chef-attribute-validator/checks/min_children.rb +37 -0
  15. data/lib/chef-attribute-validator/checks/regex.rb +31 -0
  16. data/lib/chef-attribute-validator/checks/required.rb +42 -0
  17. data/lib/chef-attribute-validator/checks/type.rb +48 -0
  18. data/lib/chef-attribute-validator/rule.rb +54 -0
  19. data/lib/chef-attribute-validator/version.rb +7 -0
  20. data/lib/chef-attribute-validator/violation.rb +17 -0
  21. data/test/fixtures/attr_set.rb +10 -0
  22. data/test/fixtures/check_child_count.rb +104 -0
  23. data/test/fixtures/check_looks_like_arg_ip.rb +6 -0
  24. data/test/fixtures/check_looks_like_arg_regex.rb +6 -0
  25. data/test/fixtures/check_looks_like_arg_url.rb +6 -0
  26. data/test/fixtures/check_looks_like_arg_your_mom.rb +6 -0
  27. data/test/fixtures/check_looks_like_ip.rb +57 -0
  28. data/test/fixtures/check_looks_like_url.rb +39 -0
  29. data/test/fixtures/check_regex_assorted.rb +42 -0
  30. data/test/fixtures/check_regex_literal.rb +5 -0
  31. data/test/fixtures/check_regex_nil.rb +5 -0
  32. data/test/fixtures/check_regex_object.rb +5 -0
  33. data/test/fixtures/check_regex_string.rb +5 -0
  34. data/test/fixtures/check_required_assorted.rb +82 -0
  35. data/test/fixtures/check_required_false.rb +5 -0
  36. data/test/fixtures/check_required_true.rb +5 -0
  37. data/test/fixtures/check_required_zero.rb +5 -0
  38. data/test/fixtures/check_type.rb +270 -0
  39. data/test/fixtures/rules_empty.rb +2 -0
  40. data/test/fixtures/rules_missing_path.rb +1 -0
  41. data/test/fixtures/rules_no_check.rb +1 -0
  42. data/test/fixtures/rules_type_and_min_children.rb +3 -0
  43. data/test/fixtures/rules_type_check.rb +2 -0
  44. data/test/unit/attr_set_spec.rb +55 -0
  45. data/test/unit/check_child_count_spec.rb +109 -0
  46. data/test/unit/check_looks_like_spec.rb +106 -0
  47. data/test/unit/check_regex_spec.rb +62 -0
  48. data/test/unit/check_required_spec.rb +106 -0
  49. data/test/unit/check_type_spec.rb +191 -0
  50. data/test/unit/rule_parse_spec.rb +93 -0
  51. data/test/unit/spec_helper.rb +18 -0
  52. metadata +170 -0
checksums.yaml ADDED
@@ -0,0 +1,15 @@
1
+ ---
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ OGU5Y2Y2ZGFjYWIwNjBjMGVhODFiMTMxYWY0NzY1NzcyMWJlMjk0ZQ==
5
+ data.tar.gz: !binary |-
6
+ M2RmYmFjNTFlYTRjZGIyMTY4YTUwNjZmNmIwYTlhNTZkNzI3YTJlOQ==
7
+ SHA512:
8
+ metadata.gz: !binary |-
9
+ NzM4NjBlNmY1MzdhNTBhNDAyNTc1YmVhMTdlMjExMTdkN2ZlZGNmMGIzZWJm
10
+ YmE0NmJkZjhhYzliOTY2ZTI3YzE1OTgyMWQ1NzA3N2NjYzYyNTdiNmRmYTZk
11
+ NDYwZmMzOGM5OWViNWVmYzJkNTZlMDM5MjZmZTE2MzQxYWExMTg=
12
+ data.tar.gz: !binary |-
13
+ ZWQ4NjdiM2Y3Nzk5NGU1NGZlMDdlODBhMTg5ZDAwOWJiMGE2ZTBhMjQ4MGUy
14
+ YzA1MGJjNzZiMDlhYzdiZTQ5MTljODRkMjNmMGFmZWE3ZDZlYWJhZjQ0NWQ5
15
+ YTc3ZTcyZTY0YTVkYzQ2Zjk4OWIzZjQwYmRmYjk3NWU2NjU5MDk=
data/.gitignore ADDED
@@ -0,0 +1,20 @@
1
+ *~
2
+ *.gem
3
+ *.rbc
4
+ .bundle
5
+ .config
6
+ coverage
7
+ InstalledFiles
8
+ Gemfile.lock
9
+ lib/bundler/man
10
+ pkg
11
+ rdoc
12
+ spec/reports
13
+ test/tmp
14
+ test/version_tmp
15
+ tmp
16
+
17
+ # YARD artifacts
18
+ .yardoc
19
+ _yardoc
20
+ doc/
data/CHANGES ADDED
@@ -0,0 +1,4 @@
1
+ 2013-11-10 Clinton Wolfe 0.1.0
2
+ Initial release
3
+ Type, Regex, Min/Max Children, Required, and LooksLike checks
4
+ No wildcards in paths yet, planned for 0.2.0
data/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source 'https://rubygems.org'
2
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,27 @@
1
+ Copyright (c) 2013, Clinton Wolfe
2
+ All rights reserved.
3
+
4
+ Redistribution and use in source and binary forms, with or without modification,
5
+ are permitted provided that the following conditions are met:
6
+
7
+ * Redistributions of source code must retain the above copyright notice, this
8
+ list of conditions and the following disclaimer.
9
+
10
+ * Redistributions in binary form must reproduce the above copyright notice, this
11
+ list of conditions and the following disclaimer in the documentation and/or
12
+ other materials provided with the distribution.
13
+
14
+ * Neither the name of 'chef-attribute-validator' nor the names of its
15
+ contributors may be used to endorse or promote products derived from
16
+ this software without specific prior written permission.
17
+
18
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
19
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
22
+ ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23
+ (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
24
+ LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
25
+ ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
27
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
data/README.md ADDED
@@ -0,0 +1,109 @@
1
+ # Chef::Attribute::Validator
2
+
3
+ Define, enforce, and handle violations of validation rules for Chef node
4
+ attributes. The rule definitions are themselves node attributes. This
5
+ gem provides the validation engine, and can be used outside of a
6
+ convergence run; a cookbook (attribute-validator) is planned to perform
7
+ validation during a chef run, at compile or converge time.
8
+
9
+ ## Usage
10
+
11
+ ### Define Rules
12
+
13
+ Define rules by creating attributes that specify checks to make on node
14
+ attributes that match a given path. By default the library looks for rules
15
+ under node['attribute-validator']['rules'], but you can override that.
16
+
17
+ See Defining Validation Rules.
18
+
19
+ ### From Within a Chef Recipe
20
+
21
+ To scan for violations of all rules, do:
22
+
23
+ require 'chef-attribute-validator'
24
+ violations = Chef::Attribute::Validator.new(node).validate_all
25
+
26
+ You can also apply one rule, or rules that match a regex:
27
+
28
+ violations = Chef::Attribute::Validator.new(node).validate_rule('rulename')
29
+ violations = Chef::Attribute::Validator.new(node).validate_matching(/matchme/)
30
+
31
+ The return value from each is an array, possibly empty. Each array element is a simple object, with accessors:
32
+
33
+ rule_name (string)
34
+ path (in slash format, see REFERENCING ATTRIBUTES)
35
+ message
36
+
37
+ A single rule may have many violations.
38
+
39
+ ## Defining Validation Rules
40
+
41
+ To define validation rules, create attributes under
42
+
43
+ default['attribute-validator']['rules'][<rulename>]...
44
+
45
+ Each rule gets a unique name. Each entry is hash structure with the following keys:
46
+
47
+ path - Required. Slash formatted path of the attributes to check.
48
+ enabled - Optional boolean, default true. Set to false to knock out rule.
49
+
50
+ The remaining entries describe criteria to enforce on the value of the attribute(s)
51
+ referenced by 'path'. You may list zero or more.
52
+
53
+ type - Checks type of value. One of 'string', 'number', 'boolean', 'hash', 'array'.
54
+ min_children - Integer. Fails for all but Hash and Array. For Hash and Array, minimum number of elements to be considered valid.
55
+ max_children - Integer. Fails for all but Hash and Array. For Hash and Array, maximum number of elements to be considered valid.
56
+ regex - Regexp. Applies given regex to the value. Ignored for Hash and Array. See looks_like for a selection of canned regexen.
57
+ required - Boolean. If true, fails if the path matches zero attributes, or the value is nil, or the value is the empty string, or if the value is an empty array or empty hash. No-op if false (use present => false to enforce absence).
58
+ looks_like - String, one of 'url', 'ip'. Applies canned regexes (or more sophisticated matchers).
59
+
60
+ ## Referencing Attributes
61
+
62
+ Attribute locations are described using a syntax similar to shell globs.
63
+
64
+ Given:
65
+
66
+ /foo - Matches node['foo']
67
+ /foo/bar - Matches node['foo']['bar']
68
+
69
+ ## Bugs, Limitations and Roadmap
70
+
71
+ #### Wildcard syntax not yet supported
72
+
73
+ I'd like to have at least this:
74
+
75
+ /foo/* - Matches node['foo']['bar'], but not node['foo'] or node['foo']['bar']['baz']
76
+ /foo/c* - Matches node['foo']['car'], but not node['foo']['bar']
77
+ /foo/?ar - Matches node['foo']['bar'] and node['foo']['bar'] but not node['foo']['bleh']
78
+
79
+ Possibly eventually support for **, [<charclass>], or {<alternatives>}.
80
+
81
+ #### Companion cookbook
82
+
83
+ Simple cookbook named 'attribute-validator' that loads the gem and provides recipes for compile-time and convergence-time violation checking.
84
+
85
+ ### Lame Exceptions
86
+
87
+ No real exception class, just raising a bare string exception, which could certainly be improved upon.
88
+
89
+ ### Planned checks:
90
+
91
+ looks_like/hostname
92
+ looks_like/email
93
+ name_regex - Regexp. Applies given regex to the last element in the attribute path ('basename', if you will)
94
+ present - Boolean. If true, fails if the path matches zero attributes. If false, fails if the path matches nonzero attributes. Does not consider nilness, only existence of attribute key(s). See also required.
95
+
96
+ #### Probably Slow
97
+
98
+
99
+ ## Author
100
+
101
+ Clinton Wolfe
102
+
103
+ ## Contributing
104
+
105
+ 1. Fork it (https://github.com/clintoncwolfe/chef-attribute-validator)
106
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
107
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
108
+ 4. Push to the branch (`git push origin my-new-feature`)
109
+ 5. Create new Pull Request at (https://github.com/clintoncwolfe/chef-attribute-validator)
data/Rakefile ADDED
@@ -0,0 +1,27 @@
1
+ # -*-ruby-*-
2
+
3
+ require 'rake'
4
+ #require 'rspec/core/rake_task'
5
+ require "bundler/gem_tasks"
6
+
7
+ desc "Runs all development tests"
8
+ task :test
9
+
10
+
11
+ task :test => [:syntax]
12
+ desc "Checks ruby files for syntax errors"
13
+ task :syntax do |t|
14
+ Dir.glob('**/*.rb').each do |f|
15
+ system("/bin/echo -n '#{f}: '; ruby -c #{f}")
16
+ end
17
+ end
18
+
19
+ task :test => [:unit]
20
+ desc "Runs unit tests"
21
+ #RSpec::Core::RakeTask.new(:unit) do |t|
22
+ # t.pattern = 'test/unit/**/*_spec.rb'
23
+ # t.rspec_opts = "-fd"
24
+ #end
25
+
26
+
27
+
@@ -0,0 +1,25 @@
1
+ # coding: utf-8 # -*-ruby-*-
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'chef-attribute-validator/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "chef-attribute-validator"
8
+ spec.version = Chef::Attribute::Validator::VERSION
9
+ spec.authors = ["Clinton Wolfe"]
10
+ spec.email = ["clinton@omniti.com"]
11
+ spec.description = %q{Define, enforce, and handle violations of validation rules for Chef node attributes. This gem provides the validation engine, and can be used outside of a convergence run; a cookbook (attribute-validator) is availabel to perform validation during a chef run, at compile or converge time.}
12
+ spec.summary = %q{A Rubygem implementing a rule engine for validating Chef node attributes.}
13
+ spec.homepage = "https://github.com/clintoncwolfe/chef-attribute-validator"
14
+ spec.license = "BSD (3-clause)"
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_development_dependency "bundler", "~> 1.3"
22
+ spec.add_development_dependency "rake"
23
+
24
+ spec.add_runtime_dependency "chef", "~> 11.6.0" # TODO: test with older chefs
25
+ end
@@ -0,0 +1,52 @@
1
+ require "chef-attribute-validator/version"
2
+ require "chef-attribute-validator/violation"
3
+ require "chef-attribute-validator/rule"
4
+ require "chef-attribute-validator/attribute_set"
5
+
6
+ class Chef
7
+ class Attribute
8
+ class Validator
9
+
10
+ attr_accessor :node
11
+ attr_accessor :rules
12
+
13
+ def initialize(a_node)
14
+ @node = a_node
15
+ populate_rules
16
+ end
17
+
18
+ def validate_all
19
+ violations = []
20
+ rules.each do |rulename, rule|
21
+ violations += rule.apply(node)
22
+ end
23
+ violations
24
+ end
25
+
26
+ def validate_rule(rulename)
27
+ unless rules.has_key?(rulename)
28
+ raise "No such attribute validation rule named '#{rulename}' - have rules: #{rules.keys.sort.join(',')}"
29
+ end
30
+ rules[rulename].apply(node)
31
+ end
32
+
33
+ def validate_matching(rule_regex)
34
+ violations = []
35
+ rules.select { |rn,r| rule_regex.match(rn) }.each do |rulename, rule|
36
+ violations += rule.apply(node)
37
+ end
38
+ violations
39
+ end
40
+
41
+ private
42
+
43
+ def populate_rules
44
+ @rules = {}
45
+ node['attribute-validator']['rules'].each do |rulename, ruledef|
46
+ rules[rulename] = Chef::Attribute::Validator::Rule.new(rulename, ruledef)
47
+ end
48
+ end
49
+
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,90 @@
1
+
2
+ require 'forwardable'
3
+
4
+ class Chef
5
+ class Attribute
6
+ class Validator
7
+
8
+ class AttributeSet
9
+ attr_accessor :guts
10
+ attr_accessor :path
11
+ attr_accessor :node
12
+
13
+ extend Forwardable
14
+
15
+ def_delegators :@guts, :size, :each, :empty?, :has_key?, :[], :keys, :select, :reject
16
+
17
+ def initialize(a_node, a_path)
18
+ @guts = {}
19
+ @path = a_path
20
+ @node = a_node
21
+
22
+ embowel
23
+ end
24
+
25
+ private
26
+
27
+ def embowel
28
+ # Split on /
29
+ steps = path.split('/')
30
+
31
+ # Since we begin with /, the first element is ""
32
+ if steps[0] == "" then steps.shift end
33
+
34
+ # Promote integer strings to integers
35
+ steps.map! { |s| s.match(/^\d+$/) ? s.to_i : s }
36
+
37
+ # TODO: some ckine of wildcard expansion
38
+ all_steps = [ steps ]
39
+
40
+ all_steps.each do |these_steps|
41
+ if path_exists_by_steps?(these_steps) then
42
+ guts['/' + these_steps.join('/')] = fetch_val_by_steps(these_steps)
43
+ end
44
+ end
45
+ end
46
+
47
+ def path_exists_by_steps? (the_steps)
48
+ nv = node
49
+ steps = the_steps.dup
50
+ while steps.size > 0
51
+ step = steps.shift
52
+
53
+ # binding.pry
54
+ if nv.kind_of?(Chef::Node::ImmutableArray) then
55
+ # TODO: what if the step isn't an int?
56
+
57
+ if nv.size > step then
58
+ nv = nv[step]
59
+ else
60
+ # Array not long enough
61
+ return false
62
+ end
63
+
64
+ elsif nv.respond_to?(:has_key?) then
65
+ if nv.has_key?(step) then
66
+ nv = nv[step]
67
+ else
68
+ # No such key
69
+ return false
70
+ end
71
+ else
72
+ # Must be a scalar?
73
+ return false
74
+ end
75
+ end
76
+ return true
77
+ end
78
+
79
+ def fetch_val_by_steps(the_steps)
80
+ nv = node
81
+ steps = the_steps.dup
82
+ while steps.size > 0
83
+ nv = nv[steps.shift]
84
+ end
85
+ nv
86
+ end
87
+ end
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,56 @@
1
+ class Chef
2
+ class Attribute
3
+ class Validator
4
+ class Check
5
+ attr_accessor :path_spec
6
+ attr_accessor :check_arg
7
+ attr_accessor :rule_name
8
+
9
+ def initialize(a_rule, a_check_arg)
10
+ @path_spec = a_rule.path
11
+ @rule_name = a_rule.name
12
+ @check_arg = a_check_arg
13
+ end
14
+
15
+ def self.make(check_name, a_rule, a_check_arg)
16
+ unless @@check_classes.has_key?(check_name)
17
+ raise "Unrecognized option or check type '#{check_name}' for attribute validation rule '#{a_rule.name}'"
18
+ end
19
+ checker = @@check_classes[check_name].new(a_rule, a_check_arg)
20
+ checker.validate_check_arg
21
+ checker
22
+ end
23
+
24
+ def self.list_check_types
25
+ @@check_classes.keys
26
+ end
27
+
28
+ protected
29
+
30
+ def self.register_check (check_name, klass)
31
+ @@check_classes ||= {}
32
+ @@check_classes[check_name] = klass
33
+ end
34
+
35
+ def val_scalar?(val)
36
+ [
37
+ Float,
38
+ Integer,
39
+ NilClass,
40
+ TrueClass,
41
+ FalseClass,
42
+ String,
43
+ ].any? { |k| val.kind_of?(k) }
44
+ end
45
+
46
+
47
+
48
+
49
+ end
50
+ end
51
+ end
52
+ end
53
+
54
+ Dir.glob(File.join(File.dirname(__FILE__), 'checks', '*.rb')).each do |check_implementation|
55
+ require check_implementation
56
+ end
@@ -0,0 +1,52 @@
1
+
2
+ require 'ipaddr'
3
+ require 'uri'
4
+
5
+ class Chef
6
+ class Attribute
7
+ class Validator
8
+ class Check
9
+ class LooksLike < Check
10
+
11
+ register_check('looks_like', LooksLike)
12
+
13
+ def validate_check_arg
14
+ expected = [
15
+ 'ip',
16
+ 'url',
17
+ ]
18
+
19
+ unless expected.include?(check_arg)
20
+ raise "Bad 'looks_like' check argument '#{check_arg}' for rule '#{rule_name}' - expected one of #{expected.join(',')}"
21
+ end
22
+ end
23
+
24
+ def check(attrset)
25
+ violations = []
26
+ attrset.each do |path, value|
27
+ if val_scalar?(value) then
28
+ next if value.nil?
29
+ case check_arg
30
+ when 'ip'
31
+ begin
32
+ IPAddr.new(value)
33
+ rescue
34
+ violations.push Chef::Attribute::Validator::Violation.new(rule_name, path, "Value '#{value}' does not look like an IP address")
35
+ end
36
+ when 'url'
37
+ begin
38
+ URI(value)
39
+ rescue
40
+ violations.push Chef::Attribute::Validator::Violation.new(rule_name, path, "Value '#{value}' does not look like a URL")
41
+ end
42
+ end
43
+ end
44
+ end
45
+ violations
46
+ end
47
+
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end