125 lines
3.4 KiB
Ruby
125 lines
3.4 KiB
Ruby
|
# 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
|