gitlab-org--gitlab-foss/lib/gitlab/background_migration/fix_vulnerability_occurrenc...

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, EncodingError, JSON::ParserError, 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