diff options
Diffstat (limited to 'lib/gitlab/background_migration/fix_vulnerability_occurrences_with_hashes_as_raw_metadata.rb')
-rw-r--r-- | lib/gitlab/background_migration/fix_vulnerability_occurrences_with_hashes_as_raw_metadata.rb | 124 |
1 files changed, 124 insertions, 0 deletions
diff --git a/lib/gitlab/background_migration/fix_vulnerability_occurrences_with_hashes_as_raw_metadata.rb b/lib/gitlab/background_migration/fix_vulnerability_occurrences_with_hashes_as_raw_metadata.rb new file mode 100644 index 00000000000..2b049ea2d2f --- /dev/null +++ b/lib/gitlab/background_migration/fix_vulnerability_occurrences_with_hashes_as_raw_metadata.rb @@ -0,0 +1,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 |