1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
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
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
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
116
117
118
119
120
121
122
123
124
|
# frozen_string_literal: true
require 'parser/ruby27'
module Gitlab
module BackgroundMigration
# This migration fixes raw_metadata entries which have incorrectly been passed a Ruby Hash instead of JSON data.
class FixVulnerabilityOccurrencesWithHashesAsRawMetadata
CLUSTER_IMAGE_SCANNING_REPORT_TYPE = 7
GENERIC_REPORT_TYPE = 99
# Type error is used to handle unexpected types when parsing stringified hashes.
class TypeError < ::StandardError
attr_reader :message, :type
def initialize(message, type)
@message = message
@type = type
end
end
# Migration model namespace isolated from application code.
class Finding < ActiveRecord::Base
include EachBatch
self.table_name = 'vulnerability_occurrences'
scope :by_api_report_types, -> { where(report_type: [CLUSTER_IMAGE_SCANNING_REPORT_TYPE, GENERIC_REPORT_TYPE]) }
end
def perform(start_id, end_id)
Finding.by_api_report_types.where(id: start_id..end_id).each do |finding|
next if valid_json?(finding.raw_metadata)
metadata = hash_from_s(finding.raw_metadata)
finding.update(raw_metadata: metadata.to_json) if metadata
end
mark_job_as_succeeded(start_id, end_id)
end
def hash_from_s(str_hash)
ast = Parser::Ruby27.parse(str_hash)
unless ast.type == :hash
::Gitlab::AppLogger.error(message: "expected raw_metadata to be a hash", type: ast.type)
return
end
parse_hash(ast)
rescue Parser::SyntaxError => e
::Gitlab::AppLogger.error(message: "error parsing raw_metadata", error: e.message)
nil
rescue TypeError => e
::Gitlab::AppLogger.error(message: "error parsing raw_metadata", error: e.message, type: e.type)
nil
end
private
def mark_job_as_succeeded(*arguments)
::Gitlab::Database::BackgroundMigrationJob.mark_all_as_succeeded(
'FixVulnerabilityOccurrencesWithHashesAsRawMetadata',
arguments
)
end
def valid_json?(metadata)
Oj.load(metadata)
true
rescue Oj::ParseError, Encoding::UndefinedConversionError
false
end
def parse_hash(hash)
out = {}
hash.children.each do |node|
unless node.type == :pair
raise TypeError.new("expected child of hash to be a `pair`", node.type)
end
key, value = node.children
key = parse_key(key)
value = parse_value(value)
out[key] = value
end
out
end
def parse_key(key)
case key.type
when :sym, :str, :int
key.children.first
else
raise TypeError.new("expected key to be either symbol, string, or integer", key.type)
end
end
def parse_value(value)
case value.type
when :sym, :str, :int
value.children.first
# rubocop:disable Lint/BooleanSymbol
when :true
true
when :false
false
# rubocop:enable Lint/BooleanSymbol
when :nil
nil
when :array
value.children.map { |c| parse_value(c) }
when :hash
parse_hash(value)
else
raise TypeError.new("value of a pair was an unexpected type", value.type)
end
end
end
end
end
|