Module: Rzo::App::ConfigValidation
- Included in:
- Subcommand
- Defined in:
- lib/rzo/app/config_validation.rb
Overview
Mix-in module providing configuration validation methods and safety checking. The goal is to provide useful feedback to the end user in the situation where ~/.rizzo.yaml is configured to point at directories which do not exist, have missing keys, etc... rubocop:disable Metrics/ModuleLength
Defined Under Namespace
Classes: Issue
Constant Summary collapse
- RZO_PERSONAL_CONFIG_SCHEMA =
Rizzo configuration schema for the personal configuration file at ~/.rizzo.yaml. Minimum necessary to load the complete configuration from all control repositories.
{ '$schema' => 'http://json-schema.org/draft/schema#', title: 'Personal Configuration', description: 'Rizzo personal configuration file', type: 'object', properties: { defaults: { type: 'object', }, control_repos: { type: 'array', items: { type: 'string' }, uniqueItems: true, }, }, required: ['control_repos'], }.freeze
- RZO_REPO_CONFIG_SCHEMA =
Rizzo complete configuration schema. This should move to a JSON file outside the code.
{ type: 'object', required: %w[defaults control_repos puppetmaster], properties: { defaults: { type: 'object', required: ['bootstrap_repo_path'], properties: { bootstrap_repo_path: { type: 'string', pattern: '^([a-zA-Z]:){0,1}(/[^/]+)+$', }, }, }, puppetmaster: { type: 'object', required: %w[name modulepath synced_folders], properties: { name: { type: 'array', items: { type: 'string' }, }, modulepath: { type: 'array', items: { type: 'string' }, }, synced_folders: { '$schema' => 'http://json-schema.org/draft/schema#', type: 'object', properties: { '/' => {}, patternProperties: { '^(/[^/]+)+$' => {}, }, additionalProperties: false, required: ['/'], } }, }, }, control_repos: { type: 'array', items: { type: 'string' }, uniqueItems: true, }, nodes: { type: 'array', items: { type: 'object', required: %w[name hostname ip], properties: { name: { type: 'string' }, hostname: { type: 'string' }, ip: { type: 'string' }, memory: { type: 'string', pattern: '^[0-9]+$' }, forwarded_ports: { type: 'array', items: { type: 'object', required: %w[guest host], properties: { guest: { type: 'string', pattern: '^[0-9]+$', }, host: { type: 'string', pattern: '^[0-9]+$', }, }, }, }, }, }, uniqueItems: true, } }, }.freeze
- CHECKS_PERSONAL_CONFIG =
The checks to execute, in order. Each method must return nil if there are no issues found. Otherwise, the check should return either one, or an array of Issue instances.
i[validate_personal_schema validate_control_repos].freeze
- CHECKS_REPO_CONFIG =
i[validate_schema validate_defaults_key validate_control_repos].freeze
Instance Method Summary collapse
-
#compute_issues(checks, config) ⇒ Array<Issue>
Compute Issues given a config map (base or complete), and an Array of methods to execute.
-
#validate_complete_config!(config) ⇒ Object
Validate a complete loaded configuration.
-
#validate_control_repos(config) ⇒ Issue?
Validate the top level "control_repos" key, which should have a value of Array
where each string value is a fully qualified path. -
#validate_defaults_key(config) ⇒ Issue?
Validate the configuration has a top level key named "defaults" and the value is a Hash map.
-
#validate_existence(path, prefix = '') ⇒ Issue, ...
Given a string, validate it's a fully qualified path, readable, and a git directory.
-
#validate_inform!(issues) ⇒ Object
Inform the user about issues found and exit the program.
-
#validate_personal_config!(config) ⇒ Object
Validate a personal configuration, typically originating from ~/.rizzo.yaml.
-
#validate_personal_schema(config) ⇒ Issue, ...
Validate the personal configuration, focus on ensuring the rest of the configuration can load properly.
-
#validate_schema(config) ⇒ Issue?
Validate using json-schema.
Instance Method Details
#compute_issues(checks, config) ⇒ Array<Issue>
Compute Issues given a config map (base or complete), and an Array of methods to execute.
150 151 152 153 154 155 156 157 158 159 |
# File 'lib/rzo/app/config_validation.rb', line 150 def compute_issues(checks, config) ctx = self checks.each_with_object([]) do |mth, ary| debug "Checking config for #{mth} issues" if issue = ctx.send(mth, config) # May get back an Array<Issue> or one Issue ary.concat([*issue]) end end end |
#validate_complete_config!(config) ⇒ Object
Validate a complete loaded configuration. This is distinct from a base configuration in that the YAML files in each control repository have already been merged, in order, on top of the base configuration originating at ~/.rizzo.yaml. This implements safety checking. These methods are expected to execute within the context of a Rzo::App::Subcommand instance, therefore log methods and the parsed configuration are assumed to be available.
The approach is to collect an Array of Issue instances. If issues are found, control is handed off to validate_inform! to inform the user of the issues and potentially abort the program.
190 191 192 193 194 195 196 197 |
# File 'lib/rzo/app/config_validation.rb', line 190 def validate_complete_config!(config) issues = compute_issues(CHECKS_REPO_CONFIG, config) if issues.empty? debug 'No issues detected with the complete, merged configuration.' else validate_inform!(issues) end end |
#validate_control_repos(config) ⇒ Issue?
Validate the top level "control_repos" key, which should have a value of
Array
242 243 244 245 246 247 248 249 250 251 252 253 |
# File 'lib/rzo/app/config_validation.rb', line 242 def validate_control_repos(config) if repos = config['control_repos'] return Issue.new('Top level key "control_repos" must have an Array value') unless repos.is_a? Array else return Issue.new('Top level key "control_repos" is not specified. It must be an Array of paths to your control repos.') end repos.each_with_object([]) do |pth, ary| if issue = validate_existence(pth, '#/control_repos') ary << issue end end end |
#validate_defaults_key(config) ⇒ Issue?
Validate the configuration has a top level key named "defaults" and the value is a Hash map. rubocop:disable Metrics/MethodLength
220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 |
# File 'lib/rzo/app/config_validation.rb', line 220 def validate_defaults_key(config) if defaults = config['defaults'] return Issue.new('Top level key "defaults" must have a Hash value') unless defaults.is_a? Hash else return Issue.new('Configuration does not contain top level "defaults" key') end if pth = defaults['bootstrap_repo_path'] return Issue.new('#/defaults/bootstrap_repo_path is not a String') unless pth.is_a? String else return Issue.new 'Configuration "defaults" value does not contain a '\ '"bootstrap_repo_path" key. For example, '\ '{"defaults":{"bootstrap_repo_path":"/tmp/foo"}}' end validate_existence(pth, '#/defaults/bootstrap_repo_path value of ') end |
#validate_existence(path, prefix = '') ⇒ Issue, ...
Given a string, validate it's a fully qualified path, readable, and a git directory.
261 262 263 264 265 266 267 268 |
# File 'lib/rzo/app/config_validation.rb', line 261 def validate_existence(path, prefix = '') pn = Pathname.new(path) git = pn + '.git' return Issue.new("#{prefix}#{pn} is not an absolute path. It must be fully qualified, not relative") unless pn.absolute? return Issue.new("#{prefix}#{pn} is not a directory. Has it been cloned?") unless pn.directory? return Issue.new("#{prefix}#{pn} is not readable. Are permissions correct?") unless pn.readable? return Issue.new("#{prefix}#{git} does not exist. Has #{git.dirname} been cloned properly?") unless git.directory? end |
#validate_inform!(issues) ⇒ Object
Inform the user about issues found and exit the program. The top level exception handler is not expected to display much information on validation errors. This method is expected to provide the helpful guidance.
least a key named :message
293 294 295 296 297 298 299 300 301 302 |
# File 'lib/rzo/app/config_validation.rb', line 293 def validate_inform!(issues) if opts[:validate] msg = "Validation issues found with #{opts[:config]}" exc = ErrorAndExit.new(msg, 2) exc.log_fatal = issues.each_with_object([]) { |i, a| a << i.to_s } raise exc else issues.each { |i| log.warn(i.to_s) } end end |
#validate_personal_config!(config) ⇒ Object
Validate a personal configuration, typically originating from ~/.rizzo.yaml. This configuration is necessary to build a complete control repo configuration using the top level control repo. This validation focuses on the minimum necessary configuration to bootstrap the complete configuration, primarily the repo locations and existence.
167 168 169 170 171 172 173 174 |
# File 'lib/rzo/app/config_validation.rb', line 167 def validate_personal_config!(config) issues = compute_issues(CHECKS_PERSONAL_CONFIG, config) if issues.empty? debug 'No issues detected with the personal configuration.' else validate_inform!(issues) end end |
#validate_personal_schema(config) ⇒ Issue, ...
Validate the personal configuration, focus on ensuring the rest of the configuration can load properly.
276 277 278 279 280 281 282 283 284 |
# File 'lib/rzo/app/config_validation.rb', line 276 def validate_personal_schema(config) if JSON::Validator.validate(RZO_PERSONAL_CONFIG_SCHEMA, config) debug 'No schema violations found in personal configuration file.' return nil else err_msgs = JSON::Validator.fully_validate(RZO_PERSONAL_CONFIG_SCHEMA, config) return err_msgs.map { |msg| Issue.new("Personal config problem: #{msg}") } end end |
#validate_schema(config) ⇒ Issue?
Validate using json-schema
204 205 206 207 208 209 210 211 212 |
# File 'lib/rzo/app/config_validation.rb', line 204 def validate_schema(config) if JSON::Validator.validate(RZO_REPO_CONFIG_SCHEMA, config) debug 'No schema violations found in loaded config.' return nil else err_msgs = JSON::Validator.fully_validate(RZO_REPO_CONFIG_SCHEMA, config) return err_msgs.map { |msg| Issue.new("Schema violation: #{msg}") } end end |