yaml_bot 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 690221476263c104a55fcb15652246b4d0c1428b
4
- data.tar.gz: fda86ec2dc086f27d78953d0e6e8b58c736e0cff
3
+ metadata.gz: 8169550325022501a33fe951c691a0814d753666
4
+ data.tar.gz: b28c4b536c226f143f5b8d50abca65e76b0cd1aa
5
5
  SHA512:
6
- metadata.gz: ea1a7373f40b3e1f3e98fb39c4b6c4fe625833f76abbf99f95c1e5767aac6fbafca4c5a79f7f1902041bff80c2e38ecb017796e28be70dd4c3dba45b861054f1
7
- data.tar.gz: 4abf76ffb38580f9291d879f4d3f15943bed69853d596dc46db58e89a60646cd36e568402a7b7fb15ecf15171856a758098629c1d7090d67169fcfd8cb1e0e48
6
+ metadata.gz: 8bf5698a4b75356806147d63b08535b38bd70a1d2753157a5030b7351c75fff3fd0321cede33d222013091d7fd81e5e94271b03fae53811aed99556724f98ed4
7
+ data.tar.gz: fbabbe2ba0d1c910e61ca5f735027b013be3e7ef2fe5bf7b12572e8742ccdbbea6b84e24cf01dad1b4f73143e70956322f13a54f5794bc26918807e27d727345
data/.gitignore CHANGED
@@ -9,3 +9,4 @@
9
9
  /tmp/
10
10
  *.gem
11
11
  *.log
12
+ .yamlbot.yml
data/.rubocop.yml CHANGED
@@ -4,7 +4,21 @@ Documentation:
4
4
  # Metrics/MethodLength:
5
5
  # Max: 15
6
6
 
7
+ Metrics/LineLength:
8
+ # This will disable the rule completely, regardless what other options you put
9
+ Enabled: false
10
+
11
+ Style/FrozenStringLiteralComment:
12
+ # Strings in Ruby 2.3 >= are frozen by default
13
+ Enabled: false
14
+
7
15
  Metrics/ClassLength:
8
16
  Enabled: false
9
17
  CountComments: false # count full line comments?
10
18
  Max: 100
19
+
20
+ Metrics/AbcSize:
21
+ Max: 25
22
+
23
+ Metrics/CyclomaticComplexity:
24
+ Max: 7
data/.ruby-version CHANGED
@@ -1 +1 @@
1
- 2.2.5
1
+ 2.4.1
data/.travis.yml CHANGED
@@ -1,8 +1,7 @@
1
1
  sudo: false
2
2
  language: ruby
3
3
  rvm:
4
- - 2.2.5
5
- - 2.3.1
4
+ - 2.4.1
6
5
  before_install: gem install bundler -v 1.13.0
7
6
  script:
8
7
  - bundle exec rake
data/CHANGELOG.md CHANGED
@@ -4,6 +4,12 @@ All notable changes to this project will be documented in this file.
4
4
  The format is based on [Keep a Changelog](http://keepachangelog.com/)
5
5
  and this project adheres to [Semantic Versioning](http://semver.org/).
6
6
 
7
+ # v0.3.0 - 2017-06-06
8
+ ### Changed
9
+ - Redefined the YamlBot rules specification according to [issue 22](https://github.com/skippyPeanutButter/yaml_bot/issues/22).
10
+ - Reimplemented the YamlBot API to handle new rules specification.
11
+ - Changed required ruby version to be >= 2.4.
12
+
7
13
  # v0.2.0 - 2017-01-14
8
14
  ### Added
9
15
  - `values` key has been added to the [Yamlbot Specification][values].
data/README.md CHANGED
@@ -11,13 +11,18 @@ YamlBot is not a Yaml linter, it is a Yaml format validator.
11
11
 
12
12
  Mistakes can often be made when working with yaml-based configuration. Systems
13
13
  such as travis-ci, rubocop, and other tools that utilize yaml files to govern
14
- how they work often present users with a multitude of keys that can take on
15
- many possible values. `yamlbot` allows you to feed it a set of rules that a
16
- yaml-based system follows and then validate any yaml file against those rules.
14
+ how they work often present users with a multitude of keys and options that can
15
+ take on many possible values. `yamlbot` allows users to feed it a set of rules
16
+ that a yaml-based system follows and then validate any yaml file against those
17
+ rules.
17
18
 
18
- If you have a tool that works off of a yaml configuration then you can craft
19
- your own [`.yamlbot.yml` file][yamlbot-spec], share it with others, and have
20
- them use `yamlbot` to validate their config against your specified rules.
19
+ If a tool works off of a yaml-based configuration then a rules file can be
20
+ crafted for validating the configuration is correct. Create a rules file, share
21
+ it with others, and have them use `yamlbot` to validate their configuration
22
+ against the specified rules.
23
+
24
+ See the [RULES FILE](RULES_DEFINITION.md) specification for details on creating
25
+ a `.yamlbot.yml` rules file.
21
26
 
22
27
  ### Installation
23
28
 
@@ -38,7 +43,7 @@ Or install it yourself as:
38
43
  ### Usage
39
44
 
40
45
  Create a `.yamlbot.yml` file with a set of rules that you want to validate yaml
41
- files against [yamlbot file specification][yamlbot-spec].
46
+ files against [yamlbot file specification](RULES_DEFINITION.md).
42
47
 
43
48
  Usage assuming the existence `.yamlbot.yml` in the current directory:
44
49
 
@@ -84,5 +89,3 @@ to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
84
89
 
85
90
  The gem is available as open source under the terms of the [MIT
86
91
  License](http://opensource.org/licenses/MIT).
87
-
88
- [yamlbot-spec]: https://github.com/skippyPeanutButter/yaml_bot/wiki/Rules-file-specification
@@ -0,0 +1,259 @@
1
+ # YAMLBOT RULES DEFINITION
2
+
3
+ This document details YamlBot rules specification. Using the details discussed
4
+ below, users can craft their own `.yamlbot.yml` rules for validating yaml-based
5
+ files according to a specification. Examples of yaml-based tools that can be
6
+ validated include [travis-ci](https://travis-ci.org/),
7
+ [rubocop](https://github.com/bbatsov/rubocop), and
8
+ [jervis](https://github.com/samrocketman/jervis).
9
+
10
+ The general layout of a `.yamlbot.yml` file is as follows:
11
+
12
+ ```yaml
13
+ defaults:
14
+ 'default values for rules'
15
+ rules:
16
+ - key: 'yaml.address'
17
+ required_key: true|false
18
+ and_requires: ['list of yaml addresses']
19
+ or_requires: ['list of yaml addresses']
20
+ value_whitelist: ['list of values']
21
+ value_blacklist: ['list of values']
22
+ types: ['list of types']
23
+ ```
24
+
25
+ ### Key definitions
26
+
27
+ - `defaults` - This section of the rules file is where default values can be
28
+ specified for the keys `required_key`, `and_requires`,
29
+ `or_requires`, `value_whitelist`, `value_blacklist`, and 'types'.
30
+ This key is optional if a user does not wish to specify defaults.
31
+
32
+ Note: Specific values defined under individual keys will override
33
+ any default values.
34
+
35
+ - `rules` - This section is where the rules for yaml file validation are
36
+ specified. This key is required for `YamlBot` to successfully parse
37
+ the rules file.
38
+
39
+ - `key` - This key is *required*. A rules file must be made up of a
40
+ list of keys that need to be validated. `key` requires a string value,
41
+ which denotes the name of the key being validated. If a user desires
42
+ to validate a nested key, then `.` delimiter must be used to denote
43
+ the parent-child relationship of nested keys.
44
+
45
+ Example:
46
+ Sample .yamlbot.yml
47
+
48
+ ```yaml
49
+ rules:
50
+ - key: person.age
51
+ required_key: true
52
+ types: [number]
53
+ ```
54
+
55
+ Sample yaml file being validated
56
+
57
+ ```yaml
58
+ person:
59
+ age: 43
60
+ ```
61
+
62
+ Nested keys can be specified up to `nth` level of nesting using the
63
+ `.` delimiter.
64
+
65
+ - `required_key` - This key is *required* only if it is not defined in
66
+ the `defaults` section. Every key in the list of key being
67
+ validated must be specified as required or not.
68
+
69
+ - `and_requires` - This key is optional. It determines additional keys that
70
+ *must* be part of the yaml file being validated if a
71
+ particular key is defined.
72
+
73
+ Example:
74
+ ```yaml
75
+ rules:
76
+ - key: keya
77
+ required_key: false
78
+ and_requires: [keyb]
79
+ ```
80
+ The above rules specifies that if `keya` is defined in the
81
+ yaml file being validated, then `keyb` must also be defined
82
+ for the yaml file to be successfully validated.
83
+
84
+ - `or_requires` - This key is optional, if a particular key is a required_key
85
+ and is missing, then a yaml file may still be valid if any of
86
+ the keys in the `or_requires` list exists and is properly
87
+ defined.
88
+
89
+ Example:
90
+ ```yaml
91
+ rules:
92
+ - key: keya
93
+ required_key: true
94
+ and_requires: [keyb]
95
+ ```
96
+ The above rules specifies that if `keya` is required in the
97
+ yaml file being validated and is missing, then the yaml file
98
+ will still be marked as valid if `keyb` exists in the yaml
99
+ file with a valid value.
100
+
101
+ - `value_whitelist` - This key is optional. It defines a list of possible values
102
+ that a key may take on. If this list is defined and a key
103
+ does not have a value specified within this list then that
104
+ key will fail validation.
105
+
106
+ - `value_blacklist` - This key is optional. It defines a list of values that a
107
+ key may *not* take on. If this list is defined and a key
108
+ has a value defined in this list, then that key will fail
109
+ validation.
110
+
111
+ - `types` - This key is optional. It defines a list of the possible data types
112
+ that a key's value may be.
113
+ Possible types include 'list', 'object', 'string', 'boolean',
114
+ 'number'.
115
+
116
+ Example:
117
+ ```yaml
118
+ - key: age
119
+ required_key: true
120
+ types: [number]
121
+ - key: ethnicity
122
+ required_key: false
123
+ types: [string, list]
124
+ ```
125
+
126
+ Types are denoted in yaml as follows.
127
+
128
+ *List*
129
+ key `groceries` takes an array of items as its value
130
+
131
+ ```yaml
132
+ groceries:
133
+ - apples
134
+ - bananas
135
+ - carrots
136
+ or
137
+
138
+ groceries: [apples, bananas, carrots]
139
+ ```
140
+
141
+ *Object*
142
+ key `car` takes a map/hash of keys with their own values
143
+
144
+ ```yaml
145
+ car:
146
+ wheels: 4
147
+ transmission: automatic
148
+ color: blue
149
+ ```
150
+
151
+ *String*
152
+ key 'name' takes a string literal as its value
153
+
154
+ ```yaml
155
+ name: Louis
156
+ ```
157
+
158
+ *Boolean*
159
+ key `can_fly` takes a boolean as its value
160
+
161
+ ```yaml
162
+ can_fly: false
163
+ ```
164
+
165
+ *Number*
166
+ key `age` takes an integer number as its value
167
+ key `price` takes a floating point number as its value
168
+
169
+ ```yaml
170
+ age: 2
171
+ price: 3.99
172
+ ```
173
+ ### Examples
174
+
175
+ The examples that follow specify several sample yaml files and their accompanying
176
+ `.yamlbot.yml` files used to validate them.
177
+
178
+ ```yaml
179
+ defaults:
180
+ required_key: true
181
+ rules:
182
+ - key: person
183
+ types: [object]
184
+ - key: person.name
185
+ types: [string]
186
+ - key: person.age
187
+ types: [number]
188
+ - key: person.haircolor
189
+ required_key: false
190
+ types: [string]
191
+ - key: person.race
192
+ types: [string]
193
+ value_whitelist: [dwarf, highelf, human, orc, hobbit]
194
+ ```
195
+
196
+ ```yaml
197
+ person:
198
+ name: Frodo
199
+ age: 20
200
+ race: hobbit
201
+ ```
202
+
203
+ The above yaml file would pass validation due to defining all of the required
204
+ keys with valid values.
205
+
206
+ ---
207
+
208
+ ```yaml
209
+ defaults:
210
+ required_key: false
211
+ types: [string, list]
212
+ rules:
213
+ - key: language
214
+ types: [string]
215
+ value_whitelist: [c, go, java, objective-c, python, ruby, swift]
216
+ - key: before_install
217
+ - key: install
218
+ - key: before_script
219
+ - key: script
220
+ ```
221
+
222
+ ```yaml
223
+ language: java
224
+ install:
225
+ - export JAVA_HOME='/usr/bin/java1.8/'
226
+ - mvn install
227
+ script: mvn test
228
+ ```
229
+
230
+ The above yaml file would pass validation due to defining all of the keys with
231
+ valid values. Although, an empty yaml file would've passed validation due to
232
+ no keys being marked as *required* in the `defaults` section.
233
+
234
+ ---
235
+
236
+ ```yaml
237
+ rules:
238
+ - key: bookshelf_items
239
+ required_key: true
240
+ types: [string, list]
241
+ - key: toybox_items
242
+ required_key: false
243
+ types: [string, list]
244
+ value_blacklist: [legos, barbies, hotwheels]
245
+ ```
246
+
247
+ ```yaml
248
+ toybox:
249
+ - 'barbies'
250
+ - 'batman'
251
+ - 'beanie baby'
252
+ bookshelf_items:
253
+ - 'Brittanica Encyclopedia'
254
+ - 'How to adult for dummies'
255
+ - 'YAML for beginners'
256
+ ```
257
+
258
+ The above yaml file would fail validation due to defining the `toybox_items`
259
+ key with a blacked listed value.
data/bin/yamlbot CHANGED
@@ -26,6 +26,10 @@ parser = OptionParser.new do |opts|
26
26
  options[:file] = file
27
27
  end
28
28
 
29
+ # opts.on('-c', '--no-color', 'Disable colored output') do
30
+ # options[:no_color] = true
31
+ # end
32
+
29
33
  opts.on('-h', '--help', 'help') do
30
34
  puts parser
31
35
  exit
@@ -1,8 +1,6 @@
1
1
  require 'yaml'
2
- require 'yaml_bot/logging_bot'
3
2
  require 'yaml_bot/rules_bot'
4
3
  require 'yaml_bot/validation_bot'
5
- require 'active_support/core_ext/hash/keys'
6
4
 
7
5
  module YamlBot
8
6
  class CLIBot
@@ -10,91 +8,65 @@ module YamlBot
10
8
 
11
9
  def initialize(opts = {})
12
10
  @options = opts
13
- log_file = File.new('yaml_bot.log', 'w')
14
- @logger_bot = LoggingBot.new(log_file)
15
11
  @rules_bot = RulesBot.new
16
12
  @validation_bot = ValidationBot.new
17
13
  end
18
14
 
19
15
  def run
20
16
  check_cli_options
21
- load_rules_file
22
- load_yaml_file
23
- load_logger
24
- check_rules_file
25
- scan_yaml_file
17
+ load_rules
18
+ load_yaml
19
+ validate_rules
20
+ scan_yaml
26
21
  print_results
27
22
  @validation_bot.violations.zero? ? 0 : 1
28
- ensure
29
- @logger_bot.close_log
30
23
  end
31
24
 
32
25
  private
33
26
 
34
- def load_rules_file
35
- if @options[:rules].nil?
36
- load_default_rules
27
+ def print_results
28
+ if @validation_bot.violations.positive?
29
+ puts pluralize(@validation_bot.violations,
30
+ 'violation',
31
+ 'violations')
37
32
  else
38
- load_custom_rules
33
+ puts "#{@validation_bot.violations} violations"
39
34
  end
40
35
  end
41
36
 
42
- def load_yaml_file
43
- yaml_file = YAML.load(File.open(@options[:file])).deep_symbolize_keys
44
- @validation_bot.yaml_file = yaml_file
45
- rescue StandardError => e
46
- $stderr.puts "Unable to locate yaml file #{@options[:file]}..."
47
- $stderr.puts e.message
48
- $stderr.puts e.backtrace
37
+ def load_rules
38
+ rules_file = @options[:rules] || '.yamlbot.yml'
39
+ raise IOError unless File.exist?(rules_file)
40
+ rules = YAML.load_file(rules_file)
41
+ @rules_bot.rules = rules
42
+ @validation_bot.rules = rules
43
+ rescue IOError
44
+ $stderr.puts "Unable to locate file: #{rules_file}"
45
+ $stderr.puts 'Create a .yamlbot.yml file in the current directory'
46
+ $stderr.puts 'or specify a rules file with the -r option'
49
47
  exit 1
50
48
  end
51
49
 
52
- def load_logger
53
- @rules_bot.logger = @logger_bot
54
- @validation_bot.logger = @logger_bot
50
+ def load_yaml
51
+ raise StandardError, 'No YAML file specified' if @options[:file].nil?
52
+ raise IOError unless File.exist?(@options[:file])
53
+ @validation_bot.yaml_file = YAML.load_file(@options[:file])
54
+ rescue IOError
55
+ $stderr.puts "Unable to locate file: #{@options[:file]}"
56
+ $stderr.puts 'Pass a YAML file to validate with the -f option'
57
+ exit 1
55
58
  end
56
59
 
57
- def check_rules_file
60
+ def validate_rules
61
+ puts 'Validating rules file...'
58
62
  @rules_bot.validate_rules
63
+ puts "Rules file validated...\n\n"
59
64
  end
60
65
 
61
- def scan_yaml_file
62
- @logger_bot.info 'Beginning scan...'
66
+ def scan_yaml
67
+ puts 'Beginning scan...'
63
68
  @validation_bot.scan
64
- @logger_bot.info 'Finished scanning...'
65
- end
66
-
67
- def print_results
68
- if @validation_bot.violations > 0
69
- @logger_bot.error pluralize(@validation_bot.violations,
70
- 'violation',
71
- 'violations')
72
- else
73
- @logger_bot.info "#{@validation_bot.violations} violations"
74
- end
75
- puts "Results logged to #{File.absolute_path(@logger_bot.log_file.path)}"
76
- end
77
-
78
- def load_default_rules
79
- rules_file = YAML.load(File.open('.yamlbot.yml')).deep_symbolize_keys
80
- @rules_bot.rules = rules_file
81
- @validation_bot.rules = rules_file
82
- rescue StandardError
83
- $stderr.puts 'Unable to locate .yamlbot.yml file...'
84
- $stderr.puts 'Create a .yamlbot.yml file in the current directory'
85
- $stderr.puts 'or specify a rules file with the -r option.'
86
- exit 1
87
- end
88
-
89
- def load_custom_rules
90
- rules_file = YAML.load(File.open(@options[:rules])).deep_symbolize_keys
91
- @rules_bot.rules = rules_file
92
- @validation_bot.rules = rules_file
93
- rescue StandardError => e
94
- $stderr.puts "Unable to locate rules file #{options[:rules]}..."
95
- $stderr.puts e.message
96
- $stderr.puts e.backtrace
97
- exit 1
69
+ puts "Finished scan...\n\n"
98
70
  end
99
71
 
100
72
  def pluralize(n, singular, plural = nil)
@@ -119,6 +91,7 @@ module YamlBot
119
91
  "\t-r, --rule-file rules\t\tThe rules you will be evaluating your "\
120
92
  'yaml against',
121
93
  "\t-f, --file file\t\t\tThe file to validate against",
94
+ # "\t-c, --color\t\t\tEnable colored output",
122
95
  "\t-h, --help\t\t\thelp"
123
96
  ].join("\n")
124
97
  puts msg
@@ -0,0 +1,117 @@
1
+ require 'yaml_bot/parse_bot'
2
+
3
+ module YamlBot
4
+ class KeyBot
5
+ attr_accessor :defaults, :invalid, :key, :yaml_file
6
+
7
+ TYPE_MAP = {
8
+ object: Hash,
9
+ list: Array,
10
+ string: String,
11
+ number: [Integer, Float],
12
+ boolean: [TrueClass, FalseClass]
13
+ }.freeze
14
+
15
+ VALIDATION_CHECKS = [
16
+ :check_if_key_is_required,
17
+ :check_and_requires,
18
+ :check_or_requires,
19
+ :check_val_whitelist,
20
+ :check_val_blacklist,
21
+ :check_types
22
+ ].freeze
23
+
24
+ def initialize(key, yaml_file, defaults)
25
+ @defaults = defaults || {}
26
+ @invalid = false
27
+ @key = @defaults.merge(key)
28
+ @yaml_file = yaml_file
29
+ end
30
+
31
+ def validate
32
+ VALIDATION_CHECKS.each do |method|
33
+ send(method)
34
+ break if @invalid
35
+ end
36
+ @invalid ? 1 : 0
37
+ end
38
+
39
+ def check_if_key_is_required
40
+ yaml_key = ParseBot.get_object_value(yaml_file, key['key'])
41
+ return unless key['required_key'] && yaml_key.nil?
42
+ return unless key['or_requires'].nil?
43
+ @invalid = true
44
+ puts "VIOLATION: Missing required key: #{key['key']}"
45
+ end
46
+
47
+ def check_and_requires
48
+ yaml_key = ParseBot.get_object_value(yaml_file, key['key'])
49
+ return if yaml_key.nil? || key['and_requires'].nil?
50
+ key['and_requires'].each do |k|
51
+ next unless ParseBot.get_object_value(yaml_file, k).nil?
52
+ @invalid = true
53
+ puts "VIOLATION: Key #{key['key']} requires that the following key(s)"
54
+ puts "also be defined: #{key['and_requires']}"
55
+ break
56
+ end
57
+ end
58
+
59
+ # If the key being checked, or any of
60
+ # the keys in the "or_requires" list
61
+ # are in the yaml then check passes
62
+ def check_or_requires
63
+ return if key['or_requires'].nil?
64
+ alt_key = search_for_alternate_key(key)
65
+ value = ParseBot.get_object_value(yaml_file, key['key'])
66
+ @invalid = value.nil? && alt_key.nil?
67
+ return unless @invalid
68
+ puts "VIOLATION: Key #{key['key']} or the following key(s)"
69
+ puts "must be defined: #{key['and_requires']}"
70
+ end
71
+
72
+ def check_val_whitelist
73
+ return if key['value_whitelist'].nil?
74
+ value = ParseBot.get_object_value(yaml_file, key['key'])
75
+ return if key['value_whitelist'].include?(value)
76
+ @invalid = true
77
+ puts "VIOLATION: Invalid value #{value} for whitelist #{key['value_whitelist']}\n"
78
+ end
79
+
80
+ def check_val_blacklist
81
+ return if key['value_blacklist'].nil?
82
+ value = ParseBot.get_object_value(yaml_file, key['key'])
83
+ return unless key['value_blacklist'].include?(value)
84
+ @invalid = true
85
+ puts "VIOLATION: Blacklisted value specified #{value}\n"
86
+ end
87
+
88
+ def check_types
89
+ return if key['types'].nil?
90
+ value = ParseBot.get_object_value(yaml_file, key['key'])
91
+ return if value.nil?
92
+ @invalid = !value_is_a_valid_type?(key['types'], value)
93
+ return unless @invalid
94
+ puts "VIOLATION: Invalid value type specified for key: #{key['key']}"
95
+ puts "#{value.class} given, valid types include #{key['types']}\n"
96
+ end
97
+
98
+ private
99
+
100
+ def search_for_alternate_key(key_map)
101
+ alt_key = nil
102
+ key_map['or_requires'].each do |k|
103
+ alt_key = ParseBot.get_object_value(yaml_file, k)
104
+ return alt_key unless alt_key.nil?
105
+ end
106
+ alt_key
107
+ end
108
+
109
+ def value_is_a_valid_type?(types, value)
110
+ types.any? do |type|
111
+ type_value = TYPE_MAP[type.downcase.to_sym]
112
+ return type_value.any? { |t| value.instance_of?(t) } if type_value.instance_of?(Array)
113
+ return true if value.instance_of?(type_value)
114
+ end
115
+ end
116
+ end
117
+ end
@@ -16,9 +16,10 @@ module YamlBot
16
16
 
17
17
  attr_accessor :log_file, :log_level
18
18
 
19
- def initialize(log_file, level = :info)
19
+ def initialize(log_file, level: :info, no_color: false)
20
20
  @log_file = log_file
21
21
  @log_level = level.to_sym unless valid_log_level(level)
22
+ @no_color = no_color
22
23
  end
23
24
 
24
25
  def info(message)
@@ -50,9 +51,9 @@ module YamlBot
50
51
  def emit(opts = {})
51
52
  color = opts[:color]
52
53
  message = opts[:message]
53
- print ESCAPES[color]
54
+ print ESCAPES[color] unless @no_color
54
55
  print message
55
- print ESCAPES[:reset]
56
+ print ESCAPES[:reset] unless @no_color
56
57
  print "\n"
57
58
  end
58
59
 
@@ -0,0 +1,22 @@
1
+ module YamlBot
2
+ class ParseBot
3
+ def self.get_object_value(yaml, key_addr)
4
+ if !key_addr.index('.').nil? && key_addr.index('.') >= 0
5
+ key1, key2 = key_addr.split('.', 2)
6
+ return get_object_value(yaml[key1], key2) if !yaml[key1].nil? && yaml[key1].instance_of?(Hash)
7
+ return nil
8
+ end
9
+
10
+ return to_boolean(yaml[key_addr]) if %w(true false).include?(yaml[key_addr])
11
+ yaml[key_addr]
12
+ rescue StandardError => e
13
+ puts "Caught exception #{e}!"
14
+ end
15
+
16
+ private_class_method
17
+
18
+ def self.to_boolean(val)
19
+ val == 'true'
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,8 @@
1
+ ---
2
+ - key
3
+ - required_key
4
+ - and_requires
5
+ - or_requires
6
+ - value_whitelist
7
+ - value_blacklist
8
+ - types
@@ -0,0 +1,6 @@
1
+ ---
2
+ - object
3
+ - list
4
+ - string
5
+ - number
6
+ - boolean
@@ -1,115 +1,70 @@
1
1
  require 'yaml'
2
- require 'yaml_bot/logging_bot'
3
2
  require 'yaml_bot/validation_error'
4
3
 
5
4
  module YamlBot
6
5
  class RulesBot
7
- attr_accessor :rules, :logger
6
+ attr_accessor :rules
8
7
 
9
- def initialize
10
- @rules = nil
11
- @missing_required_keys = false
12
- @missing_optional_keys = false
8
+ def initialize(rules = nil)
9
+ @rules = rules
13
10
  end
14
11
 
15
12
  def validate_rules
16
- fail_if_rules_are_not_loaded
17
- validate_root_keys
18
- check_top_level_keys
19
- logger.info 'Rules file validated.'
20
- end
21
-
22
- private
23
-
24
- def check_top_level_keys
25
- [:required, :optional].each do |key_type|
26
- next if determine_key_type(key_type)
27
- @rules[:root_keys][key_type].each do |key|
28
- name = key.keys.first
29
- check_subkeys_and_accepted_types(key[name])
30
- end
31
- end
32
- end
33
-
34
- def check_subkeys_and_accepted_types(key)
35
- validate_accepted_types(key) unless key[:accepted_types].nil?
36
- validate_keys(key[:subkeys]) unless key[:subkeys].nil?
37
- end
38
-
39
- def determine_key_type(key_type)
40
- if key_type == :required
41
- @missing_required_keys
13
+ raise ValidationError, '.yamlbot rules file is not set.' if @rules.nil?
14
+ raise ValidationError, 'rules section not defined in .yamlbot file' if @rules['rules'].nil?
15
+ validate_rules_keys(@rules['defaults']) unless @rules['defaults'].nil?
16
+ if @rules['rules'].instance_of?(Array)
17
+ validate_each_key_rule
42
18
  else
43
- @missing_optional_keys
19
+ msg = "The rules section of a rules file must define a list of keys.\n"
20
+ raise ValidationError, msg
44
21
  end
45
22
  end
46
23
 
47
- def validate_root_keys
48
- check_existance_of_root_keys_key
49
- check_existance_of_required_and_optional_keys
50
- fail_validation_if_missing_required_and_optional_keys
51
- end
52
-
53
- def check_existance_of_root_keys_key
54
- return unless @rules[:root_keys].nil?
55
- msg = "Missing 'root_keys' key\n"
56
- msg += "Rules file must specify 'root_keys' as the top level key\n"
57
- logger.error msg
58
- raise YamlBot::ValidationError, msg
59
- end
24
+ private
60
25
 
61
- def check_existance_of_required_and_optional_keys
62
- [:required, :optional].each do |key_type|
63
- if !@rules[:root_keys].instance_of?(Hash) ||
64
- @rules[:root_keys][key_type].nil?
65
- log_missing_key_type(key_type)
66
- end
26
+ def validate_each_key_rule
27
+ @rules['rules'].each do |key_map|
28
+ validate_key_map(key_map)
29
+ puts "Key: #{key_map['key']} validated."
67
30
  end
68
31
  end
69
32
 
70
- def log_missing_key_type(key_type)
71
- if key_type == :required
72
- @missing_required_keys = true
73
- logger.info 'No required keys specified.'
74
- else
75
- @missing_optional_keys = true
76
- logger.info 'No optional keys specified.'
77
- end
33
+ def validate_key_map(key_map)
34
+ validate_rules_keys(key_map)
35
+ validate_key_exists_and_is_string(key_map)
36
+ validate_required_key_exists_and_is_bool(key_map)
78
37
  end
79
38
 
80
- def fail_validation_if_missing_required_and_optional_keys
81
- return unless @missing_required_keys && @missing_optional_keys
82
- msg = "Missing both required and optional root keys.\n"
83
- msg += "Rules file must include at least one of those keys.\n"
84
- raise YamlBot::ValidationError, msg
39
+ def validate_rules_keys(key_map)
40
+ valid_keys = YAML.load_file(File.dirname(File.realpath(__FILE__)) +
41
+ '/resources/valid_rules_keys.yml')
42
+ invalid_keys = []
43
+ key_map.keys.each { |k| invalid_keys << k unless valid_keys.include?(k) }
44
+ return if invalid_keys.empty?
45
+ msg = "Invalid key(s) specified in rules file: #{invalid_keys}\n"
46
+ msg += "Valid rules keys include: #{valid_keys}\n"
47
+ raise ValidationError, msg
85
48
  end
86
49
 
87
- def validate_keys(rules)
88
- [:required, :optional].each do |key_type|
89
- next if rules[key_type].nil?
90
- rules[key_type].each do |key|
91
- check_subkeys_and_accepted_types(key)
92
- end
93
- end
50
+ def validate_key_exists_and_is_string(key_map)
51
+ return unless key_map['key'].nil? || !key_map['key'].instance_of?(String)
52
+ msg = "Missing required key 'key' within rules file.\n"
53
+ msg += "Or a key name has a value that is not a String.\n"
54
+ raise ValidationError, msg
94
55
  end
95
56
 
96
- def fail_if_rules_are_not_loaded
97
- return unless @rules.nil? && @rules.instance_of?(Hash)
98
- msg = 'Cannot validate, rules file not loaded!'
99
- raise YamlBot::ValidationError, msg
57
+ def validate_required_key_exists_and_is_bool(key_map)
58
+ merged_keys = {}.merge(key_map)
59
+ merged_keys = @rules['defaults'].merge(key_map) unless @rules['defaults'].nil?
60
+ return unless merged_keys['required_key'].nil? || !boolean?(merged_keys['required_key'])
61
+ msg = "Missing required key 'required_key' for key: #{key_map['key']}.\n"
62
+ msg += "Or 'required_key' has a value that is not a Boolean.\n"
63
+ raise ValidationError, msg
100
64
  end
101
65
 
102
- def validate_accepted_types(key_hash)
103
- file_name = File.dirname(File.realpath(__FILE__)) +
104
- '/resources/valid_accepted_types.yml'
105
- allowed_values = YAML.load(File.open(file_name))
106
-
107
- key_hash[:accepted_types].each do |type|
108
- next if allowed_values.include?(type)
109
- msg = "Invalid value for key: #{type}\n"
110
- msg += "Valid key values are #{allowed_values}"
111
- raise YamlBot::ValidationError, msg
112
- end
66
+ def boolean?(arg)
67
+ arg.instance_of?(TrueClass) || arg.instance_of?(FalseClass)
113
68
  end
114
69
  end
115
70
  end
@@ -1,111 +1,23 @@
1
1
  require 'yaml'
2
- require 'yaml_bot/logging_bot'
3
- require 'yaml_bot/rules_bot'
4
- require 'yaml_bot/validation_error'
2
+ require 'yaml_bot/key_bot'
5
3
 
6
4
  module YamlBot
7
5
  class ValidationBot
8
- attr_accessor :rules, :yaml_file, :violations, :logger
6
+ attr_accessor :rules, :yaml_file, :violations
9
7
 
10
- def initialize
11
- self.violations = 0
8
+ def initialize(rules = nil, yaml_file = nil)
9
+ @rules = rules || {}
10
+ @yaml_file = yaml_file || {}
11
+ @violations = 0
12
12
  end
13
13
 
14
14
  def scan
15
- validate_existance_of_rules_and_yaml_files
16
- [:required, :optional].each do |key_type|
17
- next if rules[:root_keys][key_type].nil?
18
- validate_keys yaml_file, rules[:root_keys][key_type], [], key_type
15
+ defaults = rules['defaults']
16
+ rules['rules'].each do |item|
17
+ key_bot = KeyBot.new(item, yaml_file, defaults)
18
+ @violations += key_bot.validate
19
19
  end
20
- end
21
-
22
- private
23
-
24
- def validate_keys(yaml, keys, parent_keys, key_type)
25
- keys.each_with_index do |key_map, index|
26
- key = key_map.keys.first
27
- ancestors = parent_keys.dup << key
28
- if yaml.keys.include?(key)
29
- validate_subkeys_or_accepted_types(yaml, key, keys, index, ancestors)
30
- else
31
- log_missing_key(key_type, ancestors)
32
- end
33
- end
34
- end
35
-
36
- def validate_subkeys_or_accepted_types(yaml, key, keys, index, ancestors)
37
- if !keys[index][key][:subkeys].nil?
38
- validate_subkeys(yaml, key, keys, index, ancestors)
39
- else
40
- validate_accepted_types_or_key_values(yaml[key],
41
- keys[index][key],
42
- ancestors)
43
- end
44
- end
45
-
46
- def validate_subkeys(yaml, key, keys, index, ancestors)
47
- [:required, :optional].each do |key_type|
48
- next if keys[index][key][:subkeys][key_type].nil?
49
- validate_keys(yaml[key],
50
- keys[index][key][:subkeys][key_type],
51
- ancestors,
52
- key_type)
53
- end
54
- end
55
-
56
- def validate_accepted_types_or_key_values(value, key_block, ancestors)
57
- types = key_block[:accepted_types]
58
- key_values = key_block[:values]
59
- validate_key_values(value, key_values, ancestors) unless key_values.nil?
60
- validate_accepted_types(value, types, ancestors) unless types.nil?
61
- end
62
-
63
- def validate_existance_of_rules_and_yaml_files
64
- return unless rules.nil? || yaml_file.nil?
65
- msg = "Rules file, or Yaml file is not set\n"
66
- raise YamlBot::ValidationError, msg
67
- end
68
-
69
- def validate_accepted_types(value, types, ancestors)
70
- if types.include?(value.class.to_s)
71
- msg = "Key: '#{ancestors.join('.')}' contains a value of a valid type "\
72
- "#{value.class}"
73
- log_successful_key_validation(msg)
74
- else
75
- msg = "Value: '#{value}' of class #{value.class} is not a valid type "\
76
- "for key: '#{ancestors.join('.')}'\n"
77
- msg += "Valid types for key '#{ancestors.join('.')}' include #{types}\n"
78
- log_failed_key_validation(msg)
79
- end
80
- end
81
-
82
- def validate_key_values(value, key_values, ancestors)
83
- if key_values.include?('*') || key_values.include?(value)
84
- msg = "Key: '#{ancestors.join('.')}' contains valid value #{value}"
85
- log_successful_key_validation(msg)
86
- else
87
- msg = "Key: '#{ancestors.join('.')}' contains invalid value #{value}\n"
88
- msg += "Valid values include #{key_values}"
89
- log_failed_key_validation(msg)
90
- end
91
- end
92
-
93
- def log_missing_key(key_type, ancestors)
94
- if key_type == :required
95
- self.violations += 1
96
- logger.error "Missing required key: '#{ancestors.join('.')}'"
97
- else
98
- logger.warn "Optional key: '#{ancestors.join('.')}' not utilized"
99
- end
100
- end
101
-
102
- def log_successful_key_validation(msg)
103
- logger.info msg
104
- end
105
-
106
- def log_failed_key_validation(msg)
107
- self.violations += 1
108
- logger.error msg
20
+ @violations
109
21
  end
110
22
  end
111
23
  end
@@ -1,3 +1,3 @@
1
1
  module YamlBot
2
- VERSION = '0.2.0'.freeze
2
+ VERSION = '0.3.0'.freeze
3
3
  end
data/new_spec.yml ADDED
@@ -0,0 +1,36 @@
1
+ defaults:
2
+ required_key: false
3
+ types: ['String', 'List']
4
+ rules:
5
+ - key: 'language'
6
+ required_key: true
7
+ types: ['String']
8
+ value_whitelist: ['android', 'go', 'python', 'ruby']
9
+ - key: 'env'
10
+ types: ['String', 'List', 'Object']
11
+ - key: 'env.global'
12
+ - key: 'env.matrix'
13
+ - key: 'before_install'
14
+ - key: 'install'
15
+ - key: 'before_script'
16
+ - key: 'script'
17
+ - key: 'branches.only'
18
+ types: ['List']
19
+ - key: 'branches.except'
20
+ types: ['List']
21
+ - key: 'jenkins.sudo'
22
+ types: ['Boolean', 'String']
23
+ - key: 'jenkins.use_gerrit'
24
+ types: ['Boolean', 'String']
25
+ - key: 'jenkins.collect.artifacts'
26
+
27
+ defaults:
28
+ <default values for rules>
29
+ rules:
30
+ - key: 'yaml address'
31
+ required_key: true|false
32
+ and_requires: ['list of yaml addresses']
33
+ or_requires: ['list of yaml addresses']
34
+ value_whitelist: ['list of values']
35
+ value_blacklist: ['list of values']
36
+ types: ['list of types']
data/yaml_bot.gemspec CHANGED
@@ -24,7 +24,5 @@ Gem::Specification.new do |spec|
24
24
  spec.add_development_dependency 'rspec', '~> 3.0'
25
25
  spec.add_development_dependency 'rubocop', '~> 0.46.0'
26
26
 
27
- spec.add_runtime_dependency 'activesupport', '~> 5.0'
28
-
29
- spec.required_ruby_version = '>=2.2'
27
+ spec.required_ruby_version = '>=2.4'
30
28
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: yaml_bot
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Luis Ortiz
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-01-15 00:00:00.000000000 Z
11
+ date: 2017-06-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -66,20 +66,6 @@ dependencies:
66
66
  - - "~>"
67
67
  - !ruby/object:Gem::Version
68
68
  version: 0.46.0
69
- - !ruby/object:Gem::Dependency
70
- name: activesupport
71
- requirement: !ruby/object:Gem::Requirement
72
- requirements:
73
- - - "~>"
74
- - !ruby/object:Gem::Version
75
- version: '5.0'
76
- type: :runtime
77
- prerelease: false
78
- version_requirements: !ruby/object:Gem::Requirement
79
- requirements:
80
- - - "~>"
81
- - !ruby/object:Gem::Version
82
- version: '5.0'
83
69
  description:
84
70
  email:
85
71
  - lortiz1145@gmail.com
@@ -100,19 +86,23 @@ files:
100
86
  - Gemfile
101
87
  - LICENSE.txt
102
88
  - README.md
89
+ - RULES_DEFINITION.md
103
90
  - Rakefile
104
91
  - _config.yml
105
92
  - bin/setup
106
93
  - bin/yamlbot
107
94
  - lib/yaml_bot.rb
108
95
  - lib/yaml_bot/cli_bot.rb
96
+ - lib/yaml_bot/key_bot.rb
109
97
  - lib/yaml_bot/logging_bot.rb
110
- - lib/yaml_bot/resources/valid_accepted_types.yml
111
- - lib/yaml_bot/resources/valid_key_values.yml
98
+ - lib/yaml_bot/parse_bot.rb
99
+ - lib/yaml_bot/resources/valid_rules_keys.yml
100
+ - lib/yaml_bot/resources/valid_types.yml
112
101
  - lib/yaml_bot/rules_bot.rb
113
102
  - lib/yaml_bot/validation_bot.rb
114
103
  - lib/yaml_bot/validation_error.rb
115
104
  - lib/yaml_bot/version.rb
105
+ - new_spec.yml
116
106
  - yaml_bot.gemspec
117
107
  homepage: https://github.com/skippyPeanutButter/yaml_bot
118
108
  licenses:
@@ -126,7 +116,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
126
116
  requirements:
127
117
  - - ">="
128
118
  - !ruby/object:Gem::Version
129
- version: '2.2'
119
+ version: '2.4'
130
120
  required_rubygems_version: !ruby/object:Gem::Requirement
131
121
  requirements:
132
122
  - - ">="
@@ -134,7 +124,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
134
124
  version: '0'
135
125
  requirements: []
136
126
  rubyforge_project:
137
- rubygems_version: 2.4.8
127
+ rubygems_version: 2.6.11
138
128
  signing_key:
139
129
  specification_version: 4
140
130
  summary: Validate YAML files according to a set of rules.
@@ -1,8 +0,0 @@
1
- ---
2
- - String
3
- - Fixnum
4
- - Array
5
- - Hash
6
- - TrueClass
7
- - FalseClass
8
- - Float
@@ -1,3 +0,0 @@
1
- ---
2
- - accepted_types
3
- - subkeys