yaml_bot 0.2.0 → 0.3.0

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 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