avrolution 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +9 -0
- data/.overcommit.yml +13 -0
- data/.rspec +3 -0
- data/.rubocop.yml +2 -0
- data/.travis.yml +7 -0
- data/CHANGELOG.md +4 -0
- data/Gemfile +7 -0
- data/LICENSE.txt +22 -0
- data/README.md +122 -0
- data/Rakefile +7 -0
- data/avrolution.gemspec +44 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/lib/avrolution/compatibility_break.rb +39 -0
- data/lib/avrolution/compatibility_breaks_file.rb +48 -0
- data/lib/avrolution/compatibility_check.rb +108 -0
- data/lib/avrolution/configuration.rb +41 -0
- data/lib/avrolution/railtie.rb +14 -0
- data/lib/avrolution/rake/add_compatibility_break_task.rb +32 -0
- data/lib/avrolution/rake/base_task.rb +35 -0
- data/lib/avrolution/rake/check_compatibility_task.rb +26 -0
- data/lib/avrolution/rake/rails_avrolution.rake +5 -0
- data/lib/avrolution/version.rb +3 -0
- data/lib/avrolution.rb +19 -0
- metadata +253 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: a77bd13e1ee60023abd3c9dc39531d825108333c
|
4
|
+
data.tar.gz: 682db54c1f2ca910df08b53e5bc762a0783c198a
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 27c73ea125534233e30fef39bb25f58b82c260924071676fc38a64b88014ad31c5b9f7fb1ec7a534f0c6814750fe7d7f2ffba1982ac91de3b14168a6f89fc9db
|
7
|
+
data.tar.gz: 9f3474ebff5d9d979f392865bcacaf3b17a62b93dace4953f9ab189cdc261bfe1916fc0ece516d18bb8d972d2bc1fead891ecd7d3b7806344fd623d47b8080aa
|
data/.gitignore
ADDED
data/.overcommit.yml
ADDED
data/.rspec
ADDED
data/.rubocop.yml
ADDED
data/.travis.yml
ADDED
data/CHANGELOG.md
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2017 Salsify, Inc
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
22
|
+
|
data/README.md
ADDED
@@ -0,0 +1,122 @@
|
|
1
|
+
# avrolution
|
2
|
+
|
3
|
+
Support for the evolution of Avro schemas stored in a schema registry.
|
4
|
+
|
5
|
+
This gem provides utilities to help with the management of Avro JSON schemas in a
|
6
|
+
schema registry. The compatibility of Avro JSON schema files can be checked
|
7
|
+
against a registry. Expected compatibility breaks can also be declared.
|
8
|
+
|
9
|
+
## Installation
|
10
|
+
|
11
|
+
Add this gem to your application's Gemfile, typically in a dev/test group:
|
12
|
+
|
13
|
+
```ruby
|
14
|
+
group :development, :test do
|
15
|
+
gem 'avrolution'
|
16
|
+
end
|
17
|
+
```
|
18
|
+
|
19
|
+
And then execute:
|
20
|
+
|
21
|
+
$ bundle install
|
22
|
+
|
23
|
+
Or install it yourself as:
|
24
|
+
|
25
|
+
$ gem install avrolution
|
26
|
+
|
27
|
+
## Configuration
|
28
|
+
|
29
|
+
The gem supports the following configuration:
|
30
|
+
|
31
|
+
* `root` - The directory to search for Avro JSON schemas (`.avsc`). This is also
|
32
|
+
the default location for the compatibility breaks file. In a Rails application,
|
33
|
+
`Avrolution.root` defaults to `Rails.root`.
|
34
|
+
* `compatibility_breaks_file` - The path to the compability breaks file. Defaults
|
35
|
+
to `#{Avrolution}.root/avro_compatibility_breaks.txt`.
|
36
|
+
* `compatibility_schema_registry_url` - The URL for the schema registry to use
|
37
|
+
for compatibility checking. `ENV['COMPATIBILITY_SCHEMA_REGISTRY_URL]` is used
|
38
|
+
as the default.
|
39
|
+
* `logger` - A logger used by the rake tasks in this gem. This does _NOT_ default
|
40
|
+
to `Rails.logger` in Rails applications.
|
41
|
+
|
42
|
+
## Usage
|
43
|
+
|
44
|
+
### Avro Compatibility Check Rake Task
|
45
|
+
|
46
|
+
There is a rake task to check the compatibility of all Avro JSON schemas under
|
47
|
+
`Avrolution.root` against a schema registry.
|
48
|
+
|
49
|
+
For Rails applications, the `avro:check_compatibility` task is automatically
|
50
|
+
defined via a Railtie.
|
51
|
+
|
52
|
+
This task does not require any arguments. It checks the
|
53
|
+
compatibility of all Avro JSON schemas found recursively under `Avrolution.root`
|
54
|
+
against the schema registry `Avroluion.compatibility_schema_registry_url` or
|
55
|
+
`ENV['COMPATIBILITY_SCHEMA_REGISTRY_URL]`.
|
56
|
+
|
57
|
+
```bash
|
58
|
+
rake avro:check_compatibility
|
59
|
+
```
|
60
|
+
|
61
|
+
If a schema is incompatible, then `Avrolution.compatibility_breaks_file` is also
|
62
|
+
consulted. If the schema is still incompatible with the last registered version
|
63
|
+
then the differences are displayed and the command to add a compatibility break
|
64
|
+
is printed.
|
65
|
+
|
66
|
+
For non-Rails projects, tasks can be defined as:
|
67
|
+
|
68
|
+
```ruby
|
69
|
+
require 'avrolution/rake/check_compatibility_task'
|
70
|
+
Avrolution::Rake::CheckCompatibilityTask.define
|
71
|
+
```
|
72
|
+
|
73
|
+
### Avro Add Compatibility Break Rake Task
|
74
|
+
|
75
|
+
There is a rake task add an entry to the `Avrolution.compatibility_breaks_file`.
|
76
|
+
|
77
|
+
This rake task accepts the following arguments:
|
78
|
+
* `name` - The full name of the Avro schema.
|
79
|
+
* `fingerprint` - The Resolution fingerprint as a hex string.
|
80
|
+
* `with_compatibility` - Optional compatibility level to use for the check and
|
81
|
+
during registration.
|
82
|
+
* `after_compatibility` - Optional compatibility level to set after registration.
|
83
|
+
|
84
|
+
(The registration support will be available in a future version of this gem.)
|
85
|
+
|
86
|
+
```bash
|
87
|
+
rake avro:add_compatibility_break name=com.salsify.alerts.example_value \
|
88
|
+
fingerprint=36a2035c15c1bbbfe895494697d1f760171d00ab4fd39d0616261bf6854374f9 \
|
89
|
+
with_compatibility=BACKWARD after_compatibility=FULL
|
90
|
+
```
|
91
|
+
|
92
|
+
For Rails applications, the `avro:add_compatibility_break` task is automatically
|
93
|
+
defined via a Railtie.
|
94
|
+
|
95
|
+
For non-Rails projects tasks can be defined as:
|
96
|
+
|
97
|
+
```ruby
|
98
|
+
require 'avrolution/rake/add_compatibility_break_task'
|
99
|
+
Avrolution::Rake::AddCompatibilityBreakTask.define
|
100
|
+
```
|
101
|
+
|
102
|
+
## Development
|
103
|
+
|
104
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then,
|
105
|
+
run `rake spec` to run the tests. You can also run `bin/console` for an
|
106
|
+
interactive prompt that will allow you to experiment.
|
107
|
+
|
108
|
+
To install this gem onto your local machine, run `bundle exec rake install`.
|
109
|
+
|
110
|
+
To release a new version, update the version number in `version.rb`, and then
|
111
|
+
run `bundle exec rake release`, which will create a git tag for the version,
|
112
|
+
push git commits and tags, and push the `.gem` file to
|
113
|
+
[rubygems.org](https://rubygems.org).
|
114
|
+
|
115
|
+
## Contributing
|
116
|
+
|
117
|
+
Bug reports and pull requests are welcome on GitHub at
|
118
|
+
https://github.com/salsify/avrolution.## License
|
119
|
+
|
120
|
+
The gem is available as open source under the terms of the
|
121
|
+
[MIT License](http://opensource.org/licenses/MIT).
|
122
|
+
|
data/Rakefile
ADDED
data/avrolution.gemspec
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'avrolution/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = 'avrolution'
|
8
|
+
spec.version = Avrolution::VERSION
|
9
|
+
spec.authors = ['Salsify, Inc']
|
10
|
+
spec.email = ['engineering@salsify.com']
|
11
|
+
|
12
|
+
spec.summary = 'Support for the evolution of Avro schemas stored in a schema registry.'
|
13
|
+
spec.description = spec.summary
|
14
|
+
spec.homepage = 'https://github.com/salsify/avrolution'
|
15
|
+
|
16
|
+
spec.license = 'MIT'
|
17
|
+
|
18
|
+
# Set 'allowed_push_post' to control where this gem can be published.
|
19
|
+
if spec.respond_to?(:metadata)
|
20
|
+
spec.metadata['allowed_push_host'] = 'https://rubygems.org'
|
21
|
+
else
|
22
|
+
raise 'RubyGems 2.0 or newer is required to protect against public gem pushes.'
|
23
|
+
end
|
24
|
+
|
25
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
26
|
+
spec.bindir = 'bin'
|
27
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
28
|
+
spec.require_paths = ['lib']
|
29
|
+
|
30
|
+
spec.add_development_dependency 'bundler', '~> 1.12'
|
31
|
+
spec.add_development_dependency 'rake', '~> 10.0'
|
32
|
+
spec.add_development_dependency 'rspec', '~> 3.4'
|
33
|
+
spec.add_development_dependency 'salsify_rubocop', '~> 0.47.2'
|
34
|
+
spec.add_development_dependency 'overcommit'
|
35
|
+
spec.add_development_dependency 'fakefs'
|
36
|
+
spec.add_development_dependency 'simplecov'
|
37
|
+
|
38
|
+
spec.add_runtime_dependency 'avro-salsify-fork', '1.9.0.5'
|
39
|
+
spec.add_runtime_dependency 'avromatic', '0.21.0.rc0' # TODO
|
40
|
+
spec.add_runtime_dependency 'diffy'
|
41
|
+
spec.add_runtime_dependency 'private_attr'
|
42
|
+
spec.add_runtime_dependency 'activesupport'
|
43
|
+
spec.add_runtime_dependency 'activemodel'
|
44
|
+
end
|
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'bundler/setup'
|
4
|
+
require 'avrolution'
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require 'irb'
|
14
|
+
IRB.start
|
data/bin/setup
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'active_model'
|
2
|
+
|
3
|
+
module Avrolution
|
4
|
+
class CompatibilityBreak
|
5
|
+
include ActiveModel::Validations
|
6
|
+
|
7
|
+
ValidationError = Class.new(StandardError)
|
8
|
+
|
9
|
+
VALID_COMPATIBILITY_VALUES = %w(BACKWARD BACKWARD_TRANSITIVE FORWARD
|
10
|
+
FORWARD_TRANSITIVE FULL FULL_TRANSITIVE NONE).map(&:freeze).freeze
|
11
|
+
NONE = 'NONE'.freeze
|
12
|
+
|
13
|
+
attr_reader :name, :fingerprint, :with_compatibility, :after_compatibility
|
14
|
+
|
15
|
+
validates_presence_of :name, :fingerprint
|
16
|
+
validates_inclusion_of :with_compatibility, in: VALID_COMPATIBILITY_VALUES, allow_nil: true
|
17
|
+
validates_inclusion_of :after_compatibility, in: VALID_COMPATIBILITY_VALUES, allow_nil: true
|
18
|
+
|
19
|
+
def initialize(name, fingerprint, with_compatibility = NONE, after_compatibility = nil, *extra)
|
20
|
+
@name = name
|
21
|
+
@fingerprint = fingerprint
|
22
|
+
@with_compatibility = with_compatibility.upcase
|
23
|
+
@after_compatibility = after_compatibility.try(:upcase)
|
24
|
+
@extra = extra
|
25
|
+
end
|
26
|
+
|
27
|
+
def key
|
28
|
+
[name, fingerprint]
|
29
|
+
end
|
30
|
+
|
31
|
+
def validate!
|
32
|
+
raise ValidationError.new(errors.full_messages.join(', ')) unless valid?
|
33
|
+
end
|
34
|
+
|
35
|
+
def line
|
36
|
+
[name, fingerprint, with_compatibility, after_compatibility].compact.join(' ')
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module Avrolution
|
2
|
+
module CompatibilityBreaksFile
|
3
|
+
|
4
|
+
NONE = 'NONE'.freeze
|
5
|
+
|
6
|
+
class DuplicateEntryError < StandardError
|
7
|
+
def initialize(key)
|
8
|
+
super("duplicate entry for key #{key}")
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.path
|
13
|
+
Avrolution.compatibility_breaks_file
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.add(name:,
|
17
|
+
fingerprint:,
|
18
|
+
with_compatibility: NONE,
|
19
|
+
after_compatibility: nil,
|
20
|
+
logger: Avrolution.logger)
|
21
|
+
|
22
|
+
compatibility_break = Avrolution::CompatibilityBreak.new(name, fingerprint, with_compatibility, after_compatibility)
|
23
|
+
compatibility_break.validate!
|
24
|
+
|
25
|
+
compatibility_breaks = load
|
26
|
+
raise DuplicateEntryError.new([name, fingerprint]) if compatibility_breaks.key?(compatibility_break.key)
|
27
|
+
|
28
|
+
line = compatibility_break.line
|
29
|
+
File.write(path, "#{line}\n", mode: 'a')
|
30
|
+
logger.info("Added #{line.inspect} to #{path}")
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.load
|
34
|
+
return {} unless File.exist?(path)
|
35
|
+
|
36
|
+
File.read(path).each_line.each_with_object({}) do |line, compatibility_breaks|
|
37
|
+
next if line.blank? || /^#/ =~ line.strip
|
38
|
+
|
39
|
+
compatibility_break = Avrolution::CompatibilityBreak.new(*line.strip.split(' '))
|
40
|
+
compatibility_break.validate!
|
41
|
+
|
42
|
+
raise DuplicateEntryError.new(compatibility_break.key) if compatibility_breaks.key?(compatibility_break.key)
|
43
|
+
|
44
|
+
compatibility_breaks[compatibility_break.key] = compatibility_break
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,108 @@
|
|
1
|
+
require 'private_attr'
|
2
|
+
require 'diffy'
|
3
|
+
require 'avromatic/schema_registry_patch'
|
4
|
+
|
5
|
+
module Avrolution
|
6
|
+
class CompatibilityCheck
|
7
|
+
extend PrivateAttr
|
8
|
+
|
9
|
+
attr_reader :incompatible_schemas
|
10
|
+
|
11
|
+
NONE = 'NONE'.freeze
|
12
|
+
FULL = 'FULL'.freeze
|
13
|
+
BACKWARD = 'BACKWARD'.freeze
|
14
|
+
FORWARD = 'FORWARD'.freeze
|
15
|
+
|
16
|
+
private_attr_reader :schema_registry, :compatibility_breaks,
|
17
|
+
:logger
|
18
|
+
|
19
|
+
def initialize(logger: Avrolution.logger)
|
20
|
+
@incompatible_schemas = []
|
21
|
+
@schema_registry = build_schema_registry
|
22
|
+
@compatibility_breaks = Avrolution::CompatibilityBreaksFile.load
|
23
|
+
@logger = logger
|
24
|
+
end
|
25
|
+
|
26
|
+
def call
|
27
|
+
check_schemas(Avrolution.root)
|
28
|
+
self
|
29
|
+
end
|
30
|
+
|
31
|
+
def success?
|
32
|
+
incompatible_schemas.empty?
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def check_schemas(path)
|
38
|
+
Dir[File.join(path, '**/*.avsc')].each do |schema_file|
|
39
|
+
check_schema_compatibility(schema_file)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def check_schema_compatibility(file)
|
44
|
+
json = File.read(file)
|
45
|
+
schema = Avro::Schema.parse(json)
|
46
|
+
return unless schema.type_sym == :record
|
47
|
+
|
48
|
+
fullname = schema.fullname
|
49
|
+
fingerprint = schema.sha256_resolution_fingerprint.to_s(16)
|
50
|
+
|
51
|
+
logger.info("Checking compatibility: #{fullname}")
|
52
|
+
compatible = schema_registry.compatible?(fullname, schema, 'latest')
|
53
|
+
|
54
|
+
if compatible.nil?
|
55
|
+
# compatible is nil if the subject is not registered
|
56
|
+
logger.info("... New schema: #{fullname}")
|
57
|
+
elsif !compatible && !compatibility_fallback(schema, fullname, fingerprint)
|
58
|
+
incompatible_schemas << file
|
59
|
+
report_incompatibility(json, schema, fullname, fingerprint)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
# For a schema that is incompatible with the latest registered schema,
|
64
|
+
# check if there is a compatibility break defined and check compatibility
|
65
|
+
# using the level defined by the break.
|
66
|
+
def compatibility_fallback(schema, fullname, fingerprint)
|
67
|
+
compatibility_break = compatibility_breaks[[fullname, fingerprint]]
|
68
|
+
|
69
|
+
if compatibility_break
|
70
|
+
logger.info("... Checking compatibility with level set to #{compatibility_break.with_compatibility}")
|
71
|
+
schema_registry.compatible?(fullname, schema, 'latest', with_compatibility: compatibility_break.with_compatibility)
|
72
|
+
else
|
73
|
+
false
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def report_incompatibility(json, schema, fullname, fingerprint)
|
78
|
+
last_json = schema_registry.subject_version(fullname)['schema']
|
79
|
+
last_schema = Avro::Schema.parse(last_json)
|
80
|
+
backward = last_schema.read?(schema)
|
81
|
+
forward = schema.read?(last_schema)
|
82
|
+
compatibility_with_last = if backward && forward
|
83
|
+
FULL
|
84
|
+
elsif backward
|
85
|
+
BACKWARD
|
86
|
+
elsif forward
|
87
|
+
FORWARD
|
88
|
+
else
|
89
|
+
NONE
|
90
|
+
end
|
91
|
+
|
92
|
+
logger.info("... Compatibility with last version: #{compatibility_with_last}")
|
93
|
+
logger.info(Diffy::Diff.new(last_json, json, context: 3).to_s) unless compatibility_with_last == FULL
|
94
|
+
|
95
|
+
compatibility = schema_registry.subject_config(fullname)['compatibility'] || schema_registry.global_config['compatibility']
|
96
|
+
logger.info("... Current compatibility level: #{compatibility}")
|
97
|
+
logger.info(
|
98
|
+
"\n To allow a compatibility break, run:\n" \
|
99
|
+
" rake avro:add_compatibility_break name=#{fullname} fingerprint=#{fingerprint} with_compatibility=#{compatibility_with_last} [after_compatibility=<LEVEL>]\n"
|
100
|
+
)
|
101
|
+
end
|
102
|
+
|
103
|
+
def build_schema_registry
|
104
|
+
AvroTurf::ConfluentSchemaRegistry.new(Avrolution.compatibility_schema_registry_url,
|
105
|
+
logger: Avrolution.logger)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module Avrolution
|
2
|
+
|
3
|
+
COMPATIBILITY_SCHEMA_REGISTRY_URL = 'COMPATIBILITY_SCHEMA_REGISTRY_URL'.freeze
|
4
|
+
|
5
|
+
class << self
|
6
|
+
# Root directory to search for schemas, and default location for
|
7
|
+
# compatibility breaks file
|
8
|
+
attr_writer :root
|
9
|
+
|
10
|
+
# Path to the compatibility breaks file. Defaults to
|
11
|
+
# #{Avrolution.root}/avro_compatibility_breaks.txt
|
12
|
+
attr_writer :compatibility_breaks_file
|
13
|
+
|
14
|
+
# The URL (including any Basic Auth) for the schema registry to use for
|
15
|
+
# compatibility checks
|
16
|
+
attr_writer :compatibility_schema_registry_url
|
17
|
+
|
18
|
+
attr_accessor :logger
|
19
|
+
end
|
20
|
+
|
21
|
+
self.logger = Avrolution::PassthruLogger.new($stdout)
|
22
|
+
|
23
|
+
def self.root
|
24
|
+
@root || raise('root must be set')
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.compatibility_breaks_file
|
28
|
+
@compatibility_breaks_file ||= "#{root}/avro_compatibility_breaks.txt"
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.compatibility_schema_registry_url
|
32
|
+
@compatibility_schema_registry_url ||= begin
|
33
|
+
raise 'compatibility_schema_registry_url must be set' unless ENV[COMPATIBILITY_SCHEMA_REGISTRY_URL]
|
34
|
+
ENV[COMPATIBILITY_SCHEMA_REGISTRY_URL]
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.configure
|
39
|
+
yield self
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module Avrolution
|
2
|
+
class Railtie < Rails::Railtie
|
3
|
+
|
4
|
+
initializer 'avrolution.configure' do
|
5
|
+
Avrolution.configure do |config|
|
6
|
+
config.root = Rails.root
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
rake_tasks do
|
11
|
+
load File.expand_path('../rake/rails_avrolution.rake', __FILE__)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'avrolution/rake/base_task'
|
2
|
+
|
3
|
+
module Avrolution
|
4
|
+
module Rake
|
5
|
+
class AddCompatibilityBreakTask < BaseTask
|
6
|
+
|
7
|
+
def initialize(*)
|
8
|
+
super
|
9
|
+
@name ||= :add_compatibility_break
|
10
|
+
@task_desc ||= 'Add an Avro schema compatibility break. Parameters: name, fingerprint, with_compatibility, after_compatibility'
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def perform
|
16
|
+
compatibility_break_args = ENV.to_h.slice('name', 'fingerprint', 'with_compatibility', 'after_compatibility').symbolize_keys
|
17
|
+
|
18
|
+
missing_args = %i(name fingerprint).select do |arg|
|
19
|
+
compatibility_break_args[arg].blank?
|
20
|
+
end
|
21
|
+
|
22
|
+
if missing_args.any?
|
23
|
+
puts missing_args.map { |arg| "#{arg} can't be blank" }.join(', ')
|
24
|
+
puts 'Usage: rake avro:add_compatibility_break name=<name> fingerprint=<fingerprint> [with_compatibility=<default:NONE>] [after_compatibility=<compatibility>]'
|
25
|
+
exit(1)
|
26
|
+
end
|
27
|
+
|
28
|
+
Avrolution::CompatibilityBreaksFile.add(**compatibility_break_args)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module Avrolution
|
2
|
+
module Rake
|
3
|
+
class BaseTask < ::Rake::TaskLib
|
4
|
+
|
5
|
+
attr_accessor :name, :task_namespace, :task_desc, :dependencies
|
6
|
+
|
7
|
+
def self.define(**options, &block)
|
8
|
+
new(**options, &block).define
|
9
|
+
end
|
10
|
+
|
11
|
+
def initialize(name: nil, dependencies: [])
|
12
|
+
@name = name
|
13
|
+
@task_namespace = :avro
|
14
|
+
@dependencies = dependencies
|
15
|
+
|
16
|
+
yield self if block_given?
|
17
|
+
end
|
18
|
+
|
19
|
+
def define
|
20
|
+
namespace task_namespace do
|
21
|
+
desc task_desc
|
22
|
+
task(name.to_sym => dependencies) do
|
23
|
+
perform
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def perform
|
31
|
+
raise NotImplementedError
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'avrolution/rake/base_task'
|
2
|
+
|
3
|
+
module Avrolution
|
4
|
+
module Rake
|
5
|
+
class CheckCompatibilityTask < BaseTask
|
6
|
+
|
7
|
+
def initialize(*)
|
8
|
+
super
|
9
|
+
@name ||= :check_compatibility
|
10
|
+
@task_desc ||= 'Check that all Avro schemas are compatible with latest registered in production'
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def perform
|
16
|
+
check = Avrolution::CompatibilityCheck.new.call
|
17
|
+
if check.success?
|
18
|
+
puts 'All schemas are compatible!'
|
19
|
+
else
|
20
|
+
puts "\nIncompatible schemas found: #{check.incompatible_schemas.join(', ')}"
|
21
|
+
exit(1)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,5 @@
|
|
1
|
+
require 'avrolution/rake/check_compatibility_task'
|
2
|
+
require 'avrolution/rake/add_compatibility_break_task'
|
3
|
+
|
4
|
+
Avrolution::Rake::AddCompatibilityBreakTask.define(dependencies: %i(environment))
|
5
|
+
Avrolution::Rake::CheckCompatibilityTask.define(dependencies: %i(environment))
|
data/lib/avrolution.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'avrolution/version'
|
2
|
+
require 'logger'
|
3
|
+
|
4
|
+
module Avrolution
|
5
|
+
class PassthruLogger < Logger
|
6
|
+
def initialize(*)
|
7
|
+
super
|
8
|
+
@formatter = ->(_severity, _time, _progname, msg) { "#{msg}\n" }
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
require 'active_support/core_ext/object/try'
|
14
|
+
require 'avrolution/configuration'
|
15
|
+
require 'avrolution/compatibility_break'
|
16
|
+
require 'avrolution/compatibility_breaks_file'
|
17
|
+
require 'avrolution/compatibility_check'
|
18
|
+
|
19
|
+
require 'avrolution/railtie' if defined?(Rails)
|
metadata
ADDED
@@ -0,0 +1,253 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: avrolution
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Salsify, Inc
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2017-04-05 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.12'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.12'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '10.0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '10.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rspec
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '3.4'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '3.4'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: salsify_rubocop
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 0.47.2
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: 0.47.2
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: overcommit
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: fakefs
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: simplecov
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ">="
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ">="
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: avro-salsify-fork
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - '='
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: 1.9.0.5
|
118
|
+
type: :runtime
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - '='
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: 1.9.0.5
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: avromatic
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - '='
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: 0.21.0.rc0
|
132
|
+
type: :runtime
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - '='
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: 0.21.0.rc0
|
139
|
+
- !ruby/object:Gem::Dependency
|
140
|
+
name: diffy
|
141
|
+
requirement: !ruby/object:Gem::Requirement
|
142
|
+
requirements:
|
143
|
+
- - ">="
|
144
|
+
- !ruby/object:Gem::Version
|
145
|
+
version: '0'
|
146
|
+
type: :runtime
|
147
|
+
prerelease: false
|
148
|
+
version_requirements: !ruby/object:Gem::Requirement
|
149
|
+
requirements:
|
150
|
+
- - ">="
|
151
|
+
- !ruby/object:Gem::Version
|
152
|
+
version: '0'
|
153
|
+
- !ruby/object:Gem::Dependency
|
154
|
+
name: private_attr
|
155
|
+
requirement: !ruby/object:Gem::Requirement
|
156
|
+
requirements:
|
157
|
+
- - ">="
|
158
|
+
- !ruby/object:Gem::Version
|
159
|
+
version: '0'
|
160
|
+
type: :runtime
|
161
|
+
prerelease: false
|
162
|
+
version_requirements: !ruby/object:Gem::Requirement
|
163
|
+
requirements:
|
164
|
+
- - ">="
|
165
|
+
- !ruby/object:Gem::Version
|
166
|
+
version: '0'
|
167
|
+
- !ruby/object:Gem::Dependency
|
168
|
+
name: activesupport
|
169
|
+
requirement: !ruby/object:Gem::Requirement
|
170
|
+
requirements:
|
171
|
+
- - ">="
|
172
|
+
- !ruby/object:Gem::Version
|
173
|
+
version: '0'
|
174
|
+
type: :runtime
|
175
|
+
prerelease: false
|
176
|
+
version_requirements: !ruby/object:Gem::Requirement
|
177
|
+
requirements:
|
178
|
+
- - ">="
|
179
|
+
- !ruby/object:Gem::Version
|
180
|
+
version: '0'
|
181
|
+
- !ruby/object:Gem::Dependency
|
182
|
+
name: activemodel
|
183
|
+
requirement: !ruby/object:Gem::Requirement
|
184
|
+
requirements:
|
185
|
+
- - ">="
|
186
|
+
- !ruby/object:Gem::Version
|
187
|
+
version: '0'
|
188
|
+
type: :runtime
|
189
|
+
prerelease: false
|
190
|
+
version_requirements: !ruby/object:Gem::Requirement
|
191
|
+
requirements:
|
192
|
+
- - ">="
|
193
|
+
- !ruby/object:Gem::Version
|
194
|
+
version: '0'
|
195
|
+
description: Support for the evolution of Avro schemas stored in a schema registry.
|
196
|
+
email:
|
197
|
+
- engineering@salsify.com
|
198
|
+
executables:
|
199
|
+
- console
|
200
|
+
- setup
|
201
|
+
extensions: []
|
202
|
+
extra_rdoc_files: []
|
203
|
+
files:
|
204
|
+
- ".gitignore"
|
205
|
+
- ".overcommit.yml"
|
206
|
+
- ".rspec"
|
207
|
+
- ".rubocop.yml"
|
208
|
+
- ".travis.yml"
|
209
|
+
- CHANGELOG.md
|
210
|
+
- Gemfile
|
211
|
+
- LICENSE.txt
|
212
|
+
- README.md
|
213
|
+
- Rakefile
|
214
|
+
- avrolution.gemspec
|
215
|
+
- bin/console
|
216
|
+
- bin/setup
|
217
|
+
- lib/avrolution.rb
|
218
|
+
- lib/avrolution/compatibility_break.rb
|
219
|
+
- lib/avrolution/compatibility_breaks_file.rb
|
220
|
+
- lib/avrolution/compatibility_check.rb
|
221
|
+
- lib/avrolution/configuration.rb
|
222
|
+
- lib/avrolution/railtie.rb
|
223
|
+
- lib/avrolution/rake/add_compatibility_break_task.rb
|
224
|
+
- lib/avrolution/rake/base_task.rb
|
225
|
+
- lib/avrolution/rake/check_compatibility_task.rb
|
226
|
+
- lib/avrolution/rake/rails_avrolution.rake
|
227
|
+
- lib/avrolution/version.rb
|
228
|
+
homepage: https://github.com/salsify/avrolution
|
229
|
+
licenses:
|
230
|
+
- MIT
|
231
|
+
metadata:
|
232
|
+
allowed_push_host: https://rubygems.org
|
233
|
+
post_install_message:
|
234
|
+
rdoc_options: []
|
235
|
+
require_paths:
|
236
|
+
- lib
|
237
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
238
|
+
requirements:
|
239
|
+
- - ">="
|
240
|
+
- !ruby/object:Gem::Version
|
241
|
+
version: '0'
|
242
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
243
|
+
requirements:
|
244
|
+
- - ">="
|
245
|
+
- !ruby/object:Gem::Version
|
246
|
+
version: '0'
|
247
|
+
requirements: []
|
248
|
+
rubyforge_project:
|
249
|
+
rubygems_version: 2.6.11
|
250
|
+
signing_key:
|
251
|
+
specification_version: 4
|
252
|
+
summary: Support for the evolution of Avro schemas stored in a schema registry.
|
253
|
+
test_files: []
|