rsmp-validator 0.1.0 → 0.3.2
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 +4 -4
- data/config/cross_rs4s.yaml +6 -0
- data/config/gem_supervisor.yaml +6 -0
- data/config/gem_tlc.yaml +6 -0
- data/config/kapsch_etx.yaml +13 -6
- data/config/lightmotion_satellite.yaml +8 -2
- data/config/semaforica_cartesio.yaml +8 -2
- data/config/sus.rb +1 -1
- data/config/swarco_itc3.yaml +8 -2
- data/config/tecsen_tmacs_supervisor.yaml +6 -0
- data/exe/rsmp-validator +2 -118
- data/lib/rsmp/validator/async_context.rb +4 -3
- data/lib/rsmp/validator/auto_node.rb +5 -15
- data/lib/rsmp/validator/cli/config_cli.rb +32 -0
- data/lib/rsmp/validator/cli/entrypoint.rb +40 -0
- data/lib/rsmp/validator/cli/run_options.rb +87 -0
- data/lib/rsmp/validator/cli/runner.rb +119 -0
- data/lib/rsmp/validator/cli/tee_io.rb +30 -0
- data/lib/rsmp/validator/cli/validator_overrides.rb +28 -0
- data/lib/rsmp/validator/compliance/config_metadata.rb +31 -0
- data/lib/rsmp/validator/compliance/config_sxls.rb +24 -0
- data/lib/rsmp/validator/compliance/report.rb +70 -0
- data/lib/rsmp/validator/compliance/report_failure.rb +45 -0
- data/lib/rsmp/validator/compliance/report_metadata.rb +101 -0
- data/lib/rsmp/validator/config_check.rb +72 -0
- data/lib/rsmp/validator/configuration/loader.rb +6 -0
- data/lib/rsmp/validator/configuration/sxls_override.rb +33 -0
- data/lib/rsmp/validator/configuration/validation.rb +1 -35
- data/lib/rsmp/validator/configuration/version_normalization.rb +56 -0
- data/lib/rsmp/validator/configuration.rb +25 -22
- data/lib/rsmp/validator/helpers/alarms.rb +1 -1
- data/lib/rsmp/validator/helpers/clock.rb +2 -2
- data/lib/rsmp/validator/helpers/connection.rb +18 -8
- data/lib/rsmp/validator/helpers/handshake.rb +5 -8
- data/lib/rsmp/validator/helpers/input.rb +3 -3
- data/lib/rsmp/validator/helpers/security.rb +1 -1
- data/lib/rsmp/validator/helpers/signal_plans.rb +5 -5
- data/lib/rsmp/validator/helpers/signal_priority.rb +8 -11
- data/lib/rsmp/validator/helpers/startup.rb +6 -20
- data/lib/rsmp/validator/helpers/status.rb +1 -1
- data/lib/rsmp/validator/lifecycle.rb +11 -4
- data/lib/rsmp/validator/options/site_test_options.rb +21 -1
- data/lib/rsmp/validator/options/supervisor_test_options.rb +50 -13
- data/lib/rsmp/validator/site_tester.rb +15 -6
- data/lib/rsmp/validator/supervisor_tester.rb +11 -5
- data/lib/rsmp/validator/tester.rb +25 -29
- data/lib/rsmp/validator/version.rb +1 -1
- data/lib/rsmp/validator/version_filter.rb +1 -1
- data/lib/rsmp/validator.rb +5 -1
- data/schemas/site_test.json +0 -6
- data/test/site/core/aggregated_status_spec.rb +5 -7
- data/test/site/core/connect_spec.rb +5 -5
- data/test/site/core/disconnect_spec.rb +10 -19
- data/test/site/core/message_buffer_spec.rb +114 -0
- data/test/site/tlc/alarm_spec.rb +7 -7
- data/test/site/tlc/clock_spec.rb +26 -26
- data/test/site/tlc/detector_logics_spec.rb +6 -6
- data/test/site/tlc/emergency_routes_spec.rb +16 -16
- data/test/site/tlc/input_spec.rb +10 -10
- data/test/site/tlc/invalid_command_spec.rb +24 -20
- data/test/site/tlc/invalid_status_spec.rb +25 -20
- data/test/site/tlc/modes_spec.rb +53 -37
- data/test/site/tlc/output_spec.rb +8 -8
- data/test/site/tlc/signal_groups_spec.rb +8 -8
- data/test/site/tlc/signal_plans_spec.rb +32 -34
- data/test/site/tlc/signal_priority_spec.rb +10 -12
- data/test/site/tlc/subscribe_spec.rb +9 -9
- data/test/site/tlc/system_spec.rb +12 -10
- data/test/site/tlc/traffic_data_spec.rb +27 -16
- data/test/site/tlc/traffic_situations_spec.rb +5 -5
- data/test/supervisor/connect_spec.rb +9 -12
- metadata +35 -6
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'yaml'
|
|
4
|
+
|
|
5
|
+
module RSMP
|
|
6
|
+
module Validator
|
|
7
|
+
module Compliance
|
|
8
|
+
# Reads stable compliance target metadata from a validator YAML config file.
|
|
9
|
+
class ConfigMetadata
|
|
10
|
+
def initialize(path)
|
|
11
|
+
@path = path
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def target
|
|
15
|
+
return {} unless @path && File.exist?(@path)
|
|
16
|
+
|
|
17
|
+
metadata = YAML.safe_load_file(@path, aliases: true)['compliance']
|
|
18
|
+
metadata.is_a?(Hash) ? stringify(metadata) : {}
|
|
19
|
+
rescue Psych::Exception
|
|
20
|
+
{}
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
private
|
|
24
|
+
|
|
25
|
+
def stringify(hash)
|
|
26
|
+
hash.transform_keys(&:to_s).transform_values { |value| value.is_a?(Hash) ? stringify(value) : value }
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RSMP
|
|
4
|
+
module Validator
|
|
5
|
+
module Compliance
|
|
6
|
+
# Formats resolved validator SXL config for compliance reports.
|
|
7
|
+
class ConfigSxls
|
|
8
|
+
def initialize(config)
|
|
9
|
+
@config = config
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def to_h
|
|
13
|
+
Array(@config && @config['sxls']).each_with_object({}) do |sxl, memo|
|
|
14
|
+
next unless sxl.is_a?(Hash)
|
|
15
|
+
|
|
16
|
+
name = sxl['name']
|
|
17
|
+
version = sxl['version']
|
|
18
|
+
memo[name.to_s] = version.to_s if name && version
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'fileutils'
|
|
4
|
+
require 'json'
|
|
5
|
+
require 'time'
|
|
6
|
+
require_relative 'report_failure'
|
|
7
|
+
require_relative 'report_metadata'
|
|
8
|
+
|
|
9
|
+
module RSMP
|
|
10
|
+
module Validator
|
|
11
|
+
module Compliance
|
|
12
|
+
# Builds the machine-readable compliance report emitted by rsmp-validator.
|
|
13
|
+
class Report
|
|
14
|
+
COMPLIANCE_SCHEMA_VERSION = 1
|
|
15
|
+
|
|
16
|
+
def initialize(assertions:, env: ENV, args: ARGV, generated_at: Time.now.utc, **options)
|
|
17
|
+
@assertions = assertions
|
|
18
|
+
@args = args
|
|
19
|
+
@generated_at = generated_at
|
|
20
|
+
@metadata = ReportMetadata.new(
|
|
21
|
+
env: env,
|
|
22
|
+
config: options[:config],
|
|
23
|
+
config_path: options[:config_path],
|
|
24
|
+
log_path: options[:log_path],
|
|
25
|
+
report_json_path: options[:report_json_path]
|
|
26
|
+
)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def self.write(path, **options)
|
|
30
|
+
report = new(**options).to_h
|
|
31
|
+
FileUtils.mkdir_p(File.dirname(path))
|
|
32
|
+
File.write(path, "#{JSON.pretty_generate(report)}\n")
|
|
33
|
+
report
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def to_h
|
|
37
|
+
{
|
|
38
|
+
'schema_version' => COMPLIANCE_SCHEMA_VERSION,
|
|
39
|
+
'generated_at' => @generated_at.iso8601,
|
|
40
|
+
'target' => @metadata.target,
|
|
41
|
+
'workflow' => @metadata.workflow,
|
|
42
|
+
'run' => @metadata.run,
|
|
43
|
+
'matrix' => @metadata.matrix,
|
|
44
|
+
'summary' => summary,
|
|
45
|
+
'failures' => failures
|
|
46
|
+
}
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
private
|
|
50
|
+
|
|
51
|
+
def summary
|
|
52
|
+
{
|
|
53
|
+
'status' => @assertions.passed? ? 'passed' : 'failed',
|
|
54
|
+
'passed' => @assertions.passed?,
|
|
55
|
+
'test_count' => @assertions.total,
|
|
56
|
+
'passed_count' => @assertions.passed.size,
|
|
57
|
+
'failed_count' => @assertions.failed.size,
|
|
58
|
+
'errored_count' => @assertions.errored.size,
|
|
59
|
+
'skipped_count' => @assertions.skipped.size,
|
|
60
|
+
'assertion_count' => @assertions.count
|
|
61
|
+
}
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def failures
|
|
65
|
+
@assertions.each_failure.map { |failure| ReportFailure.new(failure).to_h }
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RSMP
|
|
4
|
+
module Validator
|
|
5
|
+
module Compliance
|
|
6
|
+
# Converts a failed Sus assertion/error into compact JSON-friendly data.
|
|
7
|
+
class ReportFailure
|
|
8
|
+
ANSI_ESCAPE = /\e\[[0-9;]*m/
|
|
9
|
+
|
|
10
|
+
def initialize(failure)
|
|
11
|
+
@failure = failure
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def to_h
|
|
15
|
+
message = failure_message
|
|
16
|
+
{
|
|
17
|
+
'id' => failure_id,
|
|
18
|
+
'message' => clean_text(message[:text]),
|
|
19
|
+
'location' => message[:location],
|
|
20
|
+
'type' => @failure.class.name
|
|
21
|
+
}.compact
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
private
|
|
25
|
+
|
|
26
|
+
def failure_message
|
|
27
|
+
@failure.message
|
|
28
|
+
rescue StandardError => e
|
|
29
|
+
{ text: e.message, location: failure_id }
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def failure_id
|
|
33
|
+
identity = @failure.respond_to?(:identity) ? @failure.identity : nil
|
|
34
|
+
identity&.to_s
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def clean_text(text)
|
|
38
|
+
return nil unless text
|
|
39
|
+
|
|
40
|
+
text.to_s.gsub(ANSI_ESCAPE, '').strip
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'config_metadata'
|
|
4
|
+
require_relative 'config_sxls'
|
|
5
|
+
|
|
6
|
+
module RSMP
|
|
7
|
+
module Validator
|
|
8
|
+
module Compliance
|
|
9
|
+
# Builds static and runtime metadata for a compliance report.
|
|
10
|
+
class ReportMetadata
|
|
11
|
+
def initialize(env:, config: nil, config_path: nil, log_path: nil, report_json_path: nil)
|
|
12
|
+
@env = env
|
|
13
|
+
@config = config
|
|
14
|
+
@config_path = config_path
|
|
15
|
+
@log_path = log_path
|
|
16
|
+
@report_json_path = report_json_path
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def target
|
|
20
|
+
ConfigMetadata.new(config_path).target.merge(env_target_metadata)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def workflow
|
|
24
|
+
env_hash(
|
|
25
|
+
'name' => 'GITHUB_WORKFLOW',
|
|
26
|
+
'file' => 'COMPLIANCE_WORKFLOW_FILE',
|
|
27
|
+
'event' => 'GITHUB_EVENT_NAME',
|
|
28
|
+
'ref' => 'GITHUB_REF',
|
|
29
|
+
'sha' => 'GITHUB_SHA'
|
|
30
|
+
).merge(workflow_file_from_ref)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def run
|
|
34
|
+
{
|
|
35
|
+
'id' => integer_env('GITHUB_RUN_ID'),
|
|
36
|
+
'number' => integer_env('GITHUB_RUN_NUMBER'),
|
|
37
|
+
'attempt' => integer_env('GITHUB_RUN_ATTEMPT'),
|
|
38
|
+
'url' => run_url,
|
|
39
|
+
'log_artifact' => env_value('COMPLIANCE_LOG_ARTIFACT') || @log_path,
|
|
40
|
+
'report_artifact' => env_value('COMPLIANCE_REPORT_ARTIFACT') || @report_json_path
|
|
41
|
+
}.compact
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def matrix
|
|
45
|
+
values = {
|
|
46
|
+
'core' => config_value('core_version'),
|
|
47
|
+
'os' => env_value('RUNNER_OS')
|
|
48
|
+
}.compact
|
|
49
|
+
sxls = ConfigSxls.new(@config).to_h
|
|
50
|
+
values['sxls'] = sxls unless sxls.empty?
|
|
51
|
+
values
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
private
|
|
55
|
+
|
|
56
|
+
attr_reader :config_path
|
|
57
|
+
|
|
58
|
+
def env_target_metadata
|
|
59
|
+
env_hash(
|
|
60
|
+
'id' => 'COMPLIANCE_TARGET_ID',
|
|
61
|
+
'kind' => 'COMPLIANCE_TARGET_KIND',
|
|
62
|
+
'name' => 'COMPLIANCE_TARGET_NAME',
|
|
63
|
+
'product_url' => 'COMPLIANCE_PRODUCT_URL'
|
|
64
|
+
)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def workflow_file_from_ref
|
|
68
|
+
workflow_ref = env_value('GITHUB_WORKFLOW_REF')
|
|
69
|
+
match = workflow_ref&.match(%r{/(\.github/workflows/[^@]+)@})
|
|
70
|
+
match ? { 'file' => File.basename(match[1]) } : {}
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def env_hash(mapping)
|
|
74
|
+
mapping.transform_values { |name| env_value(name) }.compact
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def config_value(name)
|
|
78
|
+
@config[name] if @config.is_a?(Hash)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def integer_env(name)
|
|
82
|
+
value = env_value(name)
|
|
83
|
+
value&.to_i
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def env_value(name)
|
|
87
|
+
value = @env[name]
|
|
88
|
+
value unless value.nil? || value == ''
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def run_url
|
|
92
|
+
repository = env_value('GITHUB_REPOSITORY')
|
|
93
|
+
run_id = env_value('GITHUB_RUN_ID')
|
|
94
|
+
return nil unless repository && run_id
|
|
95
|
+
|
|
96
|
+
"https://github.com/#{repository}/actions/runs/#{run_id}"
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
require 'yaml'
|
|
2
|
+
|
|
3
|
+
module RSMP
|
|
4
|
+
module Validator
|
|
5
|
+
# Validates rsmp-validator config files and their embedded RSMP node config.
|
|
6
|
+
class ConfigCheck
|
|
7
|
+
Result = Struct.new(:path, :mode, :options, keyword_init: true)
|
|
8
|
+
|
|
9
|
+
class << self
|
|
10
|
+
def check_file(path, mode: 'auto')
|
|
11
|
+
raw = load_file(path)
|
|
12
|
+
resolved_mode = resolve_mode(mode, raw)
|
|
13
|
+
options = options_class_for(resolved_mode).new(config_settings(raw), log_settings: raw['log'])
|
|
14
|
+
|
|
15
|
+
Result.new(path: path, mode: resolved_mode, options: options)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
private
|
|
19
|
+
|
|
20
|
+
def load_file(path)
|
|
21
|
+
ensure_config_file!(path)
|
|
22
|
+
|
|
23
|
+
raw = YAML.load_file(path)
|
|
24
|
+
raise RSMP::ConfigurationError, "Config #{path} must be a hash" unless raw.is_a?(Hash) || raw.nil?
|
|
25
|
+
|
|
26
|
+
raw || {}
|
|
27
|
+
rescue Psych::SyntaxError => e
|
|
28
|
+
raise RSMP::ConfigurationError, "Cannot read config file #{path}: #{e}"
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def ensure_config_file!(path)
|
|
32
|
+
raise RSMP::ConfigurationError, 'not found' unless File.exist?(path)
|
|
33
|
+
raise RSMP::ConfigurationError, 'is not a file' unless File.file?(path)
|
|
34
|
+
raise RSMP::ConfigurationError, 'must be a YAML file (.yml or .yaml)' unless yaml_file?(path)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def yaml_file?(path)
|
|
38
|
+
%w[.yml .yaml].include?(File.extname(path).downcase)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def config_settings(raw)
|
|
42
|
+
settings = raw.dup
|
|
43
|
+
settings.delete('log')
|
|
44
|
+
settings
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def resolve_mode(mode, raw)
|
|
48
|
+
mode = mode.to_s
|
|
49
|
+
return mode if %w[site supervisor].include?(mode)
|
|
50
|
+
raise RSMP::ConfigurationError, "Unknown config mode #{mode.inspect}" unless mode == 'auto'
|
|
51
|
+
|
|
52
|
+
return 'site' if raw.key?('local_supervisor')
|
|
53
|
+
return 'supervisor' if raw.key?('local_site')
|
|
54
|
+
|
|
55
|
+
raise RSMP::ConfigurationError,
|
|
56
|
+
'Cannot infer validator config mode; use --mode site or --mode supervisor'
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def options_class_for(mode)
|
|
60
|
+
case mode
|
|
61
|
+
when 'site'
|
|
62
|
+
RSMP::Validator::SiteTest::Options
|
|
63
|
+
when 'supervisor'
|
|
64
|
+
RSMP::Validator::SupervisorTest::Options
|
|
65
|
+
else
|
|
66
|
+
raise RSMP::ConfigurationError, "Unknown config mode #{mode.inspect}"
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
@@ -27,6 +27,12 @@ module RSMP
|
|
|
27
27
|
|
|
28
28
|
def ensure_config_exists!(path, missing_message)
|
|
29
29
|
abort_with_error missing_message unless File.exist?(path)
|
|
30
|
+
abort_with_error "Config #{path} is not a file" unless File.file?(path)
|
|
31
|
+
abort_with_error "Config #{path} must be a YAML file (.yml or .yaml)" unless yaml_file?(path)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def yaml_file?(path)
|
|
35
|
+
%w[.yml .yaml].include?(File.extname(path).downcase)
|
|
30
36
|
end
|
|
31
37
|
|
|
32
38
|
def validate_config_hash!(raw, path)
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
module RSMP
|
|
2
|
+
module Validator
|
|
3
|
+
module Configuration
|
|
4
|
+
# Parses CLI SXL overrides.
|
|
5
|
+
module SxlsOverride
|
|
6
|
+
def apply_auto_node_overrides!(raw_config)
|
|
7
|
+
apply_auto_node_sxls_override!(raw_config) if sxls_override
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def apply_auto_node_sxls_override!(raw_config)
|
|
11
|
+
sxls = parse_sxls(sxls_override)
|
|
12
|
+
if mode == :supervisor
|
|
13
|
+
raw_config['sites'] ||= {}
|
|
14
|
+
raw_config['sites']['default'] ||= {}
|
|
15
|
+
raw_config['sites']['default']['sxls'] = sxls
|
|
16
|
+
else
|
|
17
|
+
raw_config['sxls'] = sxls
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def parse_sxls(value)
|
|
22
|
+
value.split(',').each_with_object({}) do |item, memo|
|
|
23
|
+
parts = item.split(':')
|
|
24
|
+
abort_with_error "Invalid --sxls item #{item.inspect}, expected name:version" unless parts.length == 2
|
|
25
|
+
|
|
26
|
+
name, version = parts
|
|
27
|
+
memo[name] = version
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
module RSMP
|
|
2
2
|
module Validator
|
|
3
3
|
module Configuration
|
|
4
|
-
# Private helpers for validating
|
|
4
|
+
# Private helpers for validating configuration values.
|
|
5
5
|
module Validation
|
|
6
6
|
private
|
|
7
7
|
|
|
@@ -75,40 +75,6 @@ module RSMP
|
|
|
75
75
|
timeouts = config['timeouts']
|
|
76
76
|
abort_with_error "Error: config 'timeouts' settings is missing or empty" if timeouts.nil? || timeouts == {}
|
|
77
77
|
end
|
|
78
|
-
|
|
79
|
-
def normalize_core_version!
|
|
80
|
-
core_version = ENV['CORE_VERSION'] || config['core_version'] || RSMP::Schema.latest_core_version
|
|
81
|
-
core_version = RSMP::Schema.latest_core_version if core_version == 'latest'
|
|
82
|
-
|
|
83
|
-
known_versions = RSMP::Schema.core_versions
|
|
84
|
-
normalized = normalized_core_version(core_version, known_versions)
|
|
85
|
-
return config['core_version'] = normalized.to_s if normalized
|
|
86
|
-
|
|
87
|
-
abort_with_error "Unknown core version #{core_version}, must be one of [#{known_versions.join(', ')}]."
|
|
88
|
-
end
|
|
89
|
-
|
|
90
|
-
def normalized_core_version(core_version, known_versions)
|
|
91
|
-
known_versions.map { |v| Gem::Version.new(v) }.sort.reverse.detect do |v|
|
|
92
|
-
Gem::Requirement.new(core_version).satisfied_by?(v)
|
|
93
|
-
end
|
|
94
|
-
end
|
|
95
|
-
|
|
96
|
-
def normalize_sxls!
|
|
97
|
-
sxls = config['sxls']
|
|
98
|
-
if sxls.nil?
|
|
99
|
-
config['sxls'] = [{ 'name' => 'tlc', 'version' => RSMP::Schema.latest_version(:tlc) }]
|
|
100
|
-
return
|
|
101
|
-
end
|
|
102
|
-
|
|
103
|
-
sxls.each do |sxl|
|
|
104
|
-
name = sxl['name']
|
|
105
|
-
abort_with_error 'SXL name cannot be core.' if name.to_s == 'core'
|
|
106
|
-
|
|
107
|
-
RSMP::Schema.find_schema! name, sxl['version'], lenient: true
|
|
108
|
-
rescue RSMP::Schema::UnknownSchemaError => e
|
|
109
|
-
abort_with_error "Unknown SXL #{name} #{sxl['version']}: #{e}"
|
|
110
|
-
end
|
|
111
|
-
end
|
|
112
78
|
end
|
|
113
79
|
end
|
|
114
80
|
end
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
module RSMP
|
|
2
|
+
module Validator
|
|
3
|
+
module Configuration
|
|
4
|
+
# Canonicalizes the versions used by filters and compliance metadata.
|
|
5
|
+
# Embedded RSMP nodes retain configured legacy spellings for wire
|
|
6
|
+
# compatibility; the rsmp gem normalizes them only for schema lookup.
|
|
7
|
+
module VersionNormalization
|
|
8
|
+
private
|
|
9
|
+
|
|
10
|
+
def normalize_core_version!
|
|
11
|
+
core_version = config['core_version'] || RSMP::Schema.latest_core_version
|
|
12
|
+
config['core_version'] = canonical_core_version(core_version)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def canonical_core_version(core_version)
|
|
16
|
+
core_version = RSMP::Schema.latest_core_version if core_version == 'latest'
|
|
17
|
+
|
|
18
|
+
known_versions = RSMP::Schema.core_versions
|
|
19
|
+
normalized = normalized_core_version(core_version, known_versions)
|
|
20
|
+
return normalized.to_s if normalized
|
|
21
|
+
|
|
22
|
+
abort_with_error "Unknown core version #{core_version}, must be one of [#{known_versions.join(', ')}]."
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def normalized_core_version(core_version, known_versions)
|
|
26
|
+
known_versions.map { |v| Gem::Version.new(v) }.sort.reverse.detect do |v|
|
|
27
|
+
Gem::Requirement.new(core_version).satisfied_by?(v)
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def normalize_sxls!
|
|
32
|
+
sxls = config['sxls']
|
|
33
|
+
if sxls.nil?
|
|
34
|
+
config['sxls'] = [{ 'name' => 'tlc', 'version' => RSMP::Schema.latest_version(:tlc) }]
|
|
35
|
+
return
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
sxls.each do |sxl|
|
|
39
|
+
name = sxl['name']
|
|
40
|
+
abort_with_error 'SXL name cannot be core.' if name.to_s == 'core'
|
|
41
|
+
|
|
42
|
+
sxl['version'] = canonical_sxl_version(name, sxl['version'])
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def canonical_sxl_version(name, version)
|
|
47
|
+
normalized = RSMP::Schema.sanitize_version(version.to_s)
|
|
48
|
+
RSMP::Schema.find_schema! name, normalized
|
|
49
|
+
normalized
|
|
50
|
+
rescue RSMP::Schema::UnknownSchemaError => e
|
|
51
|
+
abort_with_error "Unknown SXL #{name} #{version}: #{e}"
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
require 'yaml'
|
|
2
2
|
require_relative 'configuration/loader'
|
|
3
3
|
require_relative 'configuration/validation'
|
|
4
|
+
require_relative 'configuration/version_normalization'
|
|
4
5
|
require_relative 'configuration/secrets'
|
|
6
|
+
require_relative 'configuration/sxls_override'
|
|
5
7
|
|
|
6
8
|
module RSMP
|
|
7
9
|
module Validator
|
|
@@ -9,7 +11,9 @@ module RSMP
|
|
|
9
11
|
module Configuration
|
|
10
12
|
include Loader
|
|
11
13
|
include Validation
|
|
14
|
+
include VersionNormalization
|
|
12
15
|
include Secrets
|
|
16
|
+
include SxlsOverride
|
|
13
17
|
|
|
14
18
|
def load_tester_config
|
|
15
19
|
config_path = get_config_path
|
|
@@ -19,15 +23,15 @@ module RSMP
|
|
|
19
23
|
missing_message: "#{mode.capitalize} config file #{config_path} is missing"
|
|
20
24
|
)
|
|
21
25
|
|
|
22
|
-
|
|
26
|
+
apply_cli_overrides!(raw_config)
|
|
23
27
|
options = build_tester_options(raw_config, config_path)
|
|
24
28
|
apply_loaded_config(options)
|
|
25
29
|
validate_and_finalize_config!(config_path)
|
|
26
30
|
end
|
|
27
31
|
|
|
28
|
-
def
|
|
29
|
-
raw_config['core_version'] =
|
|
30
|
-
raw_config['sxls'] = parse_sxls(
|
|
32
|
+
def apply_cli_overrides!(raw_config)
|
|
33
|
+
raw_config['core_version'] = core_version_override if core_version_override
|
|
34
|
+
raw_config['sxls'] = parse_sxls(sxls_override) if sxls_override
|
|
31
35
|
end
|
|
32
36
|
|
|
33
37
|
def validate_and_finalize_config!(config_path)
|
|
@@ -49,7 +53,7 @@ module RSMP
|
|
|
49
53
|
using_message: '',
|
|
50
54
|
missing_message: "Auto #{mode} config file #{path} is missing"
|
|
51
55
|
)
|
|
52
|
-
raw_config
|
|
56
|
+
apply_auto_node_overrides!(raw_config)
|
|
53
57
|
options_class = auto_node_options_class_for(raw_config)
|
|
54
58
|
options = build_options_from_raw(raw_config, path, options_class)
|
|
55
59
|
self.auto_node_config = options.to_h
|
|
@@ -58,17 +62,17 @@ module RSMP
|
|
|
58
62
|
|
|
59
63
|
def get_config_path(local: false)
|
|
60
64
|
mode_name = mode.to_s
|
|
61
|
-
config_path =
|
|
65
|
+
config_path = config_path_option(mode_name) || get_config_path_from_validator_yaml(mode_name)
|
|
62
66
|
abort_with_error "#{mode_name.capitalize} config path not set" unless config_path && config_path != ''
|
|
63
67
|
|
|
64
68
|
config_path = File.expand_path(config_path) if local
|
|
69
|
+
self.config_path = config_path
|
|
65
70
|
config_path
|
|
66
71
|
end
|
|
67
72
|
|
|
68
73
|
def auto_node_config_path
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
return env_path if env_path && !env_path.empty?
|
|
74
|
+
option_path = auto_config_path_option(mode.to_s)
|
|
75
|
+
return option_path if option_path && !option_path.empty?
|
|
72
76
|
|
|
73
77
|
ref_path = 'config/validator.yaml'
|
|
74
78
|
return nil unless File.exist? ref_path
|
|
@@ -98,9 +102,18 @@ module RSMP
|
|
|
98
102
|
|
|
99
103
|
private
|
|
100
104
|
|
|
101
|
-
def
|
|
102
|
-
|
|
103
|
-
|
|
105
|
+
def config_path_option(mode_name)
|
|
106
|
+
case mode_name
|
|
107
|
+
when 'site' then site_config_path
|
|
108
|
+
when 'supervisor' then supervisor_config_path
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def auto_config_path_option(mode_name)
|
|
113
|
+
case mode_name
|
|
114
|
+
when 'site' then auto_site_config_path
|
|
115
|
+
when 'supervisor' then auto_supervisor_config_path
|
|
116
|
+
end
|
|
104
117
|
end
|
|
105
118
|
|
|
106
119
|
def get_config_path_from_validator_yaml(mode_name)
|
|
@@ -114,16 +127,6 @@ module RSMP
|
|
|
114
127
|
def warning(message)
|
|
115
128
|
log "Warning: #{message}", level: :warning
|
|
116
129
|
end
|
|
117
|
-
|
|
118
|
-
def parse_sxls(value)
|
|
119
|
-
value.split(',').each_with_object({}) do |item, memo|
|
|
120
|
-
parts = item.split(':')
|
|
121
|
-
abort_with_error "Invalid SXLS item #{item.inspect}, expected name:version" unless parts.length == 2
|
|
122
|
-
|
|
123
|
-
name, version = parts
|
|
124
|
-
memo[name] = version
|
|
125
|
-
end
|
|
126
|
-
end
|
|
127
130
|
end
|
|
128
131
|
end
|
|
129
132
|
end
|
|
@@ -40,7 +40,7 @@ module RSMP
|
|
|
40
40
|
end
|
|
41
41
|
|
|
42
42
|
def build_alarm_matchers(site_proxy)
|
|
43
|
-
if RSMP::Proxy.version_meets_requirement? site_proxy.core_version, '>=3.2'
|
|
43
|
+
if RSMP::Proxy.version_meets_requirement? site_proxy.core_version, '>=3.2.0'
|
|
44
44
|
[/Issue/, /Active/, /inActive/]
|
|
45
45
|
else
|
|
46
46
|
[/issue/i, /active/i, /inactive/i]
|
|
@@ -4,11 +4,11 @@ module RSMP
|
|
|
4
4
|
# Helper methods for testing RSMP clock functionality.
|
|
5
5
|
module Clock
|
|
6
6
|
def with_clock_set(site_proxy, clock, within:)
|
|
7
|
-
site_proxy.tlc.set_clock(clock, within:)
|
|
7
|
+
site_proxy.tlc.set_clock!(clock, within:)
|
|
8
8
|
site_proxy.clear_alarm_timestamps
|
|
9
9
|
yield
|
|
10
10
|
ensure
|
|
11
|
-
site_proxy.tlc.set_clock(Time.now.utc, within:)
|
|
11
|
+
site_proxy.tlc.set_clock!(Time.now.utc, within:)
|
|
12
12
|
end
|
|
13
13
|
end
|
|
14
14
|
end
|