class Avrolution::CompatibilityCheck

Constants

BACKWARD
BOTH
FORWARD
FULL
NONE

Attributes

incompatible_schemas[R]

Public Class Methods

new(logger: Avrolution.logger) click to toggle source
# File lib/avrolution/compatibility_check.rb, line 23
def initialize(logger: Avrolution.logger)
  @incompatible_schemas = []
  @schema_registry = build_schema_registry
  @compatibility_breaks = Avrolution::CompatibilityBreaksFile.load
  @logger = logger
end

Public Instance Methods

call() click to toggle source
# File lib/avrolution/compatibility_check.rb, line 30
def call
  check_schemas(Avrolution.root)
  self
end
success?() click to toggle source
# File lib/avrolution/compatibility_check.rb, line 35
def success?
  incompatible_schemas.empty?
end

Private Instance Methods

build_schema_registry() click to toggle source
# File lib/avrolution/compatibility_check.rb, line 123
def build_schema_registry
  AvroSchemaRegistry::Client.new(Avrolution.compatibility_schema_registry_url,
                                 logger: Avrolution.logger)
end
check_schema_compatibility(file) click to toggle source
# File lib/avrolution/compatibility_check.rb, line 50
def check_schema_compatibility(file)
  json = File.read(file)
  schema = Avro::Schema.parse(json)
  return unless schema.type_sym == :record

  fullname = schema.fullname
  fingerprint = schema.sha256_resolution_fingerprint.to_s(16)

  logger.info("Checking compatibility: #{fullname}")
  return if schema_registered?(fullname, schema)

  compatible = schema_registry.compatible?(fullname, schema, 'latest')

  if compatible.nil?
    # compatible is nil if the subject is not registered
    logger.info("... New schema: #{fullname}")
  elsif !compatible && !compatibility_fallback(schema, fullname, fingerprint)
    incompatible_schemas << file
    report_incompatibility(json, schema, fullname, fingerprint)
  end
end
check_schemas(path) click to toggle source
# File lib/avrolution/compatibility_check.rb, line 41
def check_schemas(path)
  vendor_bundle_path = File.join(path, 'vendor/bundle/')
  Dir[File.join(path, '**/*.avsc')].reject do |file|
    file.start_with?(vendor_bundle_path)
  end.each do |schema_file|
    check_schema_compatibility(schema_file)
  end
end
compatibility_fallback(schema, fullname, fingerprint) click to toggle source

For a schema that is incompatible with the latest registered schema, check if there is a compatibility break defined and check compatibility using the level defined by the break.

# File lib/avrolution/compatibility_check.rb, line 81
def compatibility_fallback(schema, fullname, fingerprint)
  compatibility_break = compatibility_breaks[[fullname, fingerprint]]

  if compatibility_break
    logger.info("... Checking compatibility with level set to #{compatibility_break.with_compatibility}")
    schema_registry.compatible?(
      fullname, schema, 'latest', with_compatibility: compatibility_break.with_compatibility
    )
  else
    false
  end
end
report_incompatibility(json, schema, fullname, fingerprint) click to toggle source
# File lib/avrolution/compatibility_check.rb, line 94
def report_incompatibility(json, schema, fullname, fingerprint)
  last_json = schema_registry.subject_version(fullname)['schema']
  last_schema = Avro::Schema.parse(last_json)
  backward = schema.read?(last_schema)
  forward = last_schema.read?(schema)
  compatibility_with_last = if backward && forward
                              FULL
                            elsif backward
                              BACKWARD
                            elsif forward
                              FORWARD
                            else
                              NONE
                            end

  logger.info("... Compatibility with last version: #{compatibility_with_last}")
  logger.info(Diffy::Diff.new(last_json, json, context: 3).to_s) unless compatibility_with_last == FULL

  compatibility = schema_registry.subject_config(fullname)['compatibility'] ||
    schema_registry.global_config['compatibility']
  compatibility = FULL if compatibility == BOTH
  logger.info("... Current compatibility level: #{compatibility}")
  logger.info(
    "\n  To allow a compatibility break, run:\n" \
    "    rake avro:add_compatibility_break name=#{fullname} fingerprint=#{fingerprint} " \
    "with_compatibility=#{compatibility_with_last} [after_compatibility=<LEVEL>]\n"
  )
end
schema_registered?(fullname, schema) click to toggle source
# File lib/avrolution/compatibility_check.rb, line 72
def schema_registered?(fullname, schema)
  schema_registry.lookup_subject_schema(fullname, schema)
rescue Excon::Errors::NotFound
  nil
end