Class: RailVerdict::Baseline
- Inherits:
-
Object
- Object
- RailVerdict::Baseline
- Defined in:
- lib/rail_verdict/baseline.rb
Defined Under Namespace
Classes: IncompatibleError
Constant Summary collapse
- SCHEMA_VERSION =
"1.0"- DEFAULT_FILENAME =
".railverdict-baseline.json"
Instance Attribute Summary collapse
-
#analyzer_versions ⇒ Object
readonly
Returns the value of attribute analyzer_versions.
-
#configuration_digest ⇒ Object
readonly
Returns the value of attribute configuration_digest.
-
#created_at ⇒ Object
readonly
Returns the value of attribute created_at.
-
#entries ⇒ Object
readonly
Returns the value of attribute entries.
-
#hash ⇒ Object
readonly
Returns the value of attribute hash.
Class Method Summary collapse
- .create(findings:, configuration:, analyzer_versions:, clock: Time.now.utc) ⇒ Object
- .default_path(repository_root) ⇒ Object
- .read(path) ⇒ Object
- .read_optional(path) ⇒ Object
- .resolve_path(repository_root:, configuration:, output_override: nil) ⇒ Object
- .validate_hash(hash) ⇒ Object
- .write(path:, baseline:, force: false) ⇒ Object
Instance Method Summary collapse
- #entry_by_fingerprint ⇒ Object
- #fingerprint_set ⇒ Object
-
#initialize(hash) ⇒ Baseline
constructor
A new instance of Baseline.
- #to_h ⇒ Object
Constructor Details
#initialize(hash) ⇒ Baseline
Returns a new instance of Baseline.
20 21 22 23 24 25 26 27 |
# File 'lib/rail_verdict/baseline.rb', line 20 def initialize(hash) @hash = hash.freeze @entries = hash.fetch("entries").freeze @created_at = hash.fetch("created_at").freeze @configuration_digest = hash.fetch("configuration_digest").freeze @analyzer_versions = hash.fetch("analyzer_versions").freeze freeze end |
Instance Attribute Details
#analyzer_versions ⇒ Object (readonly)
Returns the value of attribute analyzer_versions.
18 19 20 |
# File 'lib/rail_verdict/baseline.rb', line 18 def analyzer_versions @analyzer_versions end |
#configuration_digest ⇒ Object (readonly)
Returns the value of attribute configuration_digest.
18 19 20 |
# File 'lib/rail_verdict/baseline.rb', line 18 def configuration_digest @configuration_digest end |
#created_at ⇒ Object (readonly)
Returns the value of attribute created_at.
18 19 20 |
# File 'lib/rail_verdict/baseline.rb', line 18 def created_at @created_at end |
#entries ⇒ Object (readonly)
Returns the value of attribute entries.
18 19 20 |
# File 'lib/rail_verdict/baseline.rb', line 18 def entries @entries end |
#hash ⇒ Object (readonly)
Returns the value of attribute hash.
18 19 20 |
# File 'lib/rail_verdict/baseline.rb', line 18 def hash @hash end |
Class Method Details
.create(findings:, configuration:, analyzer_versions:, clock: Time.now.utc) ⇒ Object
41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 |
# File 'lib/rail_verdict/baseline.rb', line 41 def self.create(findings:, configuration:, analyzer_versions:, clock: Time.now.utc) created_at = clock.utc.iso8601 entries = findings.sort_by(&:sort_key).map do |finding| { "fingerprint" => finding.fingerprint, "analyzer" => finding.analyzer, "rule_id" => finding.rule_id, "path" => finding.location.fetch("path"), "message" => finding., "first_seen" => created_at } end entries = entries.uniq { |entry| entry.fetch("fingerprint") }.sort_by { |entry| entry.fetch("fingerprint") } hash = { "schema_version" => SCHEMA_VERSION, "fingerprint_version" => Fingerprint::VERSION, "algorithm" => Fingerprint::ALGORITHM, "payload_schema" => Fingerprint::PAYLOAD_SCHEMA, "created_at" => created_at, "created_by" => "railverdict #{RailVerdict::VERSION}", "configuration_digest" => configuration.digest, "analyzer_versions" => analyzer_versions.transform_keys(&:to_s).sort.to_h, "entries" => entries } errors = SchemaValidator.validate_baseline(hash) raise IncompatibleError, "baseline validation failed: #{errors.join('; ')}" unless errors.empty? new(hash) end |
.default_path(repository_root) ⇒ Object
71 72 73 |
# File 'lib/rail_verdict/baseline.rb', line 71 def self.default_path(repository_root) File.join(File.realpath(repository_root), DEFAULT_FILENAME) end |
.read(path) ⇒ Object
117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 |
# File 'lib/rail_verdict/baseline.rb', line 117 def self.read(path) unless File.file?(path) raise IncompatibleError, "baseline not found at #{path}" end raw = File.binread(path) begin data = JSON.parse(raw) rescue JSON::ParserError => error raise IncompatibleError, "baseline at #{path} is corrupted: #{error.}; re-create with `railverdict baseline create`" end errors = SchemaValidator.validate_baseline(data) unless errors.empty? raise IncompatibleError, "baseline at #{path} is incompatible: #{errors.join('; ')}; re-create with `railverdict baseline create`" end fingerprints = data.fetch("entries").map { |entry| entry.fetch("fingerprint") } if fingerprints.uniq.length != fingerprints.length raise IncompatibleError, "baseline at #{path} contains duplicate fingerprints; re-create with `railverdict baseline create`" end new(data) end |
.read_optional(path) ⇒ Object
138 139 140 141 142 |
# File 'lib/rail_verdict/baseline.rb', line 138 def self.read_optional(path) return nil unless File.file?(path) read(path) end |
.resolve_path(repository_root:, configuration:, output_override: nil) ⇒ Object
75 76 77 78 79 80 81 82 83 84 85 86 |
# File 'lib/rail_verdict/baseline.rb', line 75 def self.resolve_path(repository_root:, configuration:, output_override: nil) root = File.realpath(repository_root) if output_override && !output_override.to_s.strip.empty? path = output_override.to_s return Pathname.new(path).absolute? ? path : File.(path, root) end if configuration.respond_to?(:baseline_path) && configuration.baseline_path configured = configuration.baseline_path.to_s return Pathname.new(configured).absolute? ? configured : File.(configured, root) end File.join(root, DEFAULT_FILENAME) end |
.validate_hash(hash) ⇒ Object
144 145 146 147 148 149 |
# File 'lib/rail_verdict/baseline.rb', line 144 def self.validate_hash(hash) errors = SchemaValidator.validate_baseline(hash) raise IncompatibleError, errors.join("; ") unless errors.empty? hash end |
.write(path:, baseline:, force: false) ⇒ Object
88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 |
# File 'lib/rail_verdict/baseline.rb', line 88 def self.write(path:, baseline:, force: false) if File.exist?(path) && !force raise UsageError, "baseline already exists at #{path}; use --force to overwrite" end dir = File.dirname(File.(path)) FileUtils.mkdir_p(dir) tmp = File.join(dir, ".#{File.basename(path)}.tmp.#{Process.pid}.#{SecureRandom.hex(8)}") begin File.open(tmp, "wb", 0o600) do |file| file.write(JSON.pretty_generate(baseline.to_h) + "\n") file.flush file.fsync end errors = SchemaValidator.validate_baseline(baseline.to_h) raise IncompatibleError, "baseline validation failed: #{errors.join('; ')}" unless errors.empty? File.rename(tmp, path) begin dir_fd = File.open(dir, "r") dir_fd.fsync dir_fd.close rescue StandardError nil end ensure File.unlink(tmp) if File.exist?(tmp) end end |
Instance Method Details
#entry_by_fingerprint ⇒ Object
37 38 39 |
# File 'lib/rail_verdict/baseline.rb', line 37 def entry_by_fingerprint @entry_by_fingerprint ||= @entries.to_h { |entry| [entry.fetch("fingerprint"), entry] }.freeze end |
#fingerprint_set ⇒ Object
33 34 35 |
# File 'lib/rail_verdict/baseline.rb', line 33 def fingerprint_set @fingerprint_set ||= Set.new(@entries.map { |entry| entry.fetch("fingerprint") }).freeze end |
#to_h ⇒ Object
29 30 31 |
# File 'lib/rail_verdict/baseline.rb', line 29 def to_h @hash end |