avro_turf 0.7.2 → 0.11.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 0df3c290be95bcafd27a4473ba75df36a5275a6b
4
- data.tar.gz: 17e1a0283f0479c60a691d3c52e33fb8eaf3f30c
2
+ SHA256:
3
+ metadata.gz: 1e2ee4d9598bcaa2ec5738a1130fae0b19be7b5e7250f27540313b7471f97e23
4
+ data.tar.gz: 0b441cb30a153958c2ea283300a1a05c26218e2a97cb807fce36f8ad9d0240da
5
5
  SHA512:
6
- metadata.gz: 7df0d445557d08c28a9a6e0357dea7b76159846ab3f3e456b1200e4a28c84bb1853727d4259bcf94432567f8031cfb34e80b30963b5fb1e1f43fff5ce0a52e1e
7
- data.tar.gz: 144873d0859cb6c937898b94ca3649dca34284b9fa16ebaa122244faef4c20d838c871ab51ecf91c9c69d803d84fd55215867544ac57d5283a86b18fbc8ffc9f
6
+ metadata.gz: 12779eac5c325752cfa1be34da94ef5f332490cda4ff0aef29529a00557008cecf39592396a3f3525a2a12cb67a46744800781e0da69d4bf02511f5a2284e5e7
7
+ data.tar.gz: a2e4c84fb338d62296aefb8ae8c206c262d756475ec11bd9cead17bfe6015ea069ff36a891df3381385fa650cb4b5ea1584855fccc5294f5910ab34563ad973a
@@ -0,0 +1,36 @@
1
+ version: 2
2
+ jobs:
3
+ build:
4
+ environment:
5
+ CIRCLE_ARTIFACTS: /tmp/circleci-artifacts
6
+ CIRCLE_TEST_REPORTS: /tmp/circleci-test-results
7
+ docker:
8
+ - image: circleci/ruby:2.6.2
9
+ steps:
10
+ - checkout
11
+ - run: mkdir -p $CIRCLE_ARTIFACTS $CIRCLE_TEST_REPORTS
12
+ - restore_cache:
13
+ keys:
14
+ # This branch if available
15
+ - v1-dep-{{ .Branch }}-
16
+ # Default branch if not
17
+ - v1-dep-master-
18
+ # Any branch if there are none on the default branch - this should be unnecessary if you have your default branch configured correctly
19
+ - v1-dep-
20
+ - run: gem install bundler --no-document
21
+ - run: 'bundle check --path=vendor/bundle || bundle install --path=vendor/bundle --jobs=4 --retry=3'
22
+ # Save dependency cache
23
+ - save_cache:
24
+ key: v1-dep-{{ .Branch }}-{{ epoch }}
25
+ paths:
26
+ - vendor/bundle
27
+ - ~/.bundle
28
+ - run: mkdir -p $CIRCLE_TEST_REPORTS/rspec
29
+ - run:
30
+ command: bundle exec rspec --color --require spec_helper --format progress
31
+ - store_test_results:
32
+ path: /tmp/circleci-test-results
33
+ - store_artifacts:
34
+ path: /tmp/circleci-artifacts
35
+ - store_artifacts:
36
+ path: /tmp/circleci-test-results
@@ -0,0 +1,20 @@
1
+ name: Ruby
2
+
3
+ on: [push]
4
+
5
+ jobs:
6
+ build:
7
+
8
+ runs-on: ubuntu-latest
9
+
10
+ steps:
11
+ - uses: actions/checkout@v1
12
+ - name: Set up Ruby 2.6
13
+ uses: actions/setup-ruby@v1
14
+ with:
15
+ ruby-version: 2.6.x
16
+ - name: Build and test with RSpec
17
+ run: |
18
+ gem install bundler
19
+ bundle install --jobs 4 --retry 3
20
+ bundle exec rspec
@@ -0,0 +1,34 @@
1
+ # avro_turf
2
+
3
+ ## Unreleased
4
+
5
+ ## v0.11.0
6
+
7
+ - Add proxy support (#107)
8
+ - Adding support for client certs (#109)
9
+
10
+ ## v0.10.0
11
+
12
+ - Add more disk caching (#103)
13
+ - Include schema information when decoding (#100, #101, #104)
14
+
15
+ ## v0.9.0
16
+
17
+ - Compatibility with Avro v1.9.0 (#94)
18
+ - Disable the auto registeration of schema (#95)
19
+ - abstracted caching from CachedConfluentSchemaRegistry (#74)
20
+ - Load avro-patches if installed to silence deprecation errors (#85)
21
+ - Make schema store to be thread safe (#92)
22
+
23
+ ## v0.8.1
24
+
25
+ - Allow accessing schema store from outside AvroTurf (#68).
26
+
27
+ ## v0.8.0
28
+
29
+ - The names `AvroTurf::SchemaRegistry`, `AvroTurf::CachedSchemaRegistry`, and
30
+ `FakeSchemaRegistryServer` are deprecated and will be removed in a future release.
31
+ Use `AvroTurf::ConfluentSchemaRegistry`, `AvroTurf::CachedConfluentSchemaRegistry`,
32
+ and `FakeConfluentSchemaRegistryServer` instead.
33
+ - Add support for the Config API (http://docs.confluent.io/3.1.2/schema-registry/docs/api.html#config)
34
+ to `AvroTurf::ConfluentSchemaRegistry`.
data/Gemfile CHANGED
@@ -2,6 +2,3 @@ source 'https://rubygems.org'
2
2
 
3
3
  # Specify your gem's dependencies in avro_turf.gemspec
4
4
  gemspec
5
-
6
- # Used by CircleCI to format RSpec results.
7
- gem 'rspec_junit_formatter', :git => 'git@github.com:circleci/rspec_junit_formatter.git'
data/README.md CHANGED
@@ -5,6 +5,17 @@ AvroTurf is a library that makes it easier to encode and decode data using the [
5
5
  * Provides an idiomatic Ruby interface.
6
6
  * Allows referencing schemas defined in another file.
7
7
 
8
+ ## Deprecation Notice
9
+
10
+ The `AvroTurf::SchemaRegistry`, `AvroTurf::CachedSchemaRegistry`,
11
+ and `FakeSchemaRegistryServer` names have been deprecated because the Avro spec recently
12
+ introduced an incompatible [single-message encoding format](https://github.com/apache/avro/commit/30408a9c192c5f4eaaf42f01f0ffbfffd705aa57).
13
+
14
+ These classes have been renamed to `AvroTurf::ConfluentSchemaRegistry`,
15
+ `AvroTurf::CachedConfluentSchemaRegistry`, and `FakeConfluentSchemaRegistry`.
16
+
17
+ The aliases for the original names will be removed in a future release.
18
+
8
19
  ## Installation
9
20
 
10
21
  Add this line to your application's Gemfile:
@@ -80,11 +91,8 @@ In the example above, the `person` schema references the `address` schema, even
80
91
  ```json
81
92
  // person_list.avsc
82
93
  {
83
- "name": "person_list",
84
- "type": {
85
- "type": "array",
86
- "items": "person"
87
- }
94
+ "type": "array",
95
+ "items": "person"
88
96
  }
89
97
  ```
90
98
 
@@ -116,16 +124,39 @@ avro = AvroTurf::Messaging.new(registry_url: "http://my-registry:8081/")
116
124
  # time a schema is used.
117
125
  data = avro.encode({ "title" => "hello, world" }, schema_name: "greeting")
118
126
 
127
+ # If you don't want to automatically register new schemas, you can pass explicitly
128
+ # subject and version to specify which schema should be used for encoding.
129
+ # It will fetch that schema from the registry and cache it. Subsequent instances
130
+ # of the same schema version will be served by the cache.
131
+ data = avro.encode({ "title" => "hello, world" }, subject: 'greeting', version: 1)
132
+
133
+ # You can also pass explicitly schema_id to specify which schema
134
+ # should be used for encoding.
135
+ # It will fetch that schema from the registry and cache it. Subsequent instances
136
+ # of the same schema version will be served by the cache.
137
+ data = avro.encode({ "title" => "hello, world" }, schema_id: 2)
138
+
119
139
  # When decoding, the schema will be fetched from the registry and cached. Subsequent
120
140
  # instances of the same schema id will be served by the cache.
121
141
  avro.decode(data) #=> { "title" => "hello, world" }
142
+
143
+ # If you want to get decoded message as well as the schema used to encode the message,
144
+ # you can use `#decode_message` method.
145
+ result = avro.decode_message(data)
146
+ result.message #=> { "title" => "hello, world" }
147
+ result.schema_id #=> 3
148
+ result.writer_schema #=> #<Avro::Schema: ...>
149
+ result.reader_schema #=> nil
122
150
  ```
123
151
 
124
- In addition to encoding and decoding data, you can check whether a schema is compatible
125
- with a subject in the registry using the [Compatibility API](http://docs.confluent.io/2.0.0/schema-registry/docs/api.html#compatibility)
152
+ ### Confluent Schema Registry Client
153
+
154
+ The ConfluentSchemaRegistry client used by the Messaging API can also be used directly.
155
+ It can check whether a schema is compatible with a subject in the registry using the [Compatibility API](http://docs.confluent.io/3.1.2/schema-registry/docs/api.html#compatibility):
126
156
 
127
157
  ```ruby
128
- require 'avro_turf/messaging'
158
+ require 'avro_turf'
159
+ require 'avro_turf/confluent_schema_registry'
129
160
 
130
161
  schema = <<-JSON
131
162
  {
@@ -144,15 +175,22 @@ schema = <<-JSON
144
175
  }
145
176
  JSON
146
177
 
147
- avro = AvroTurf::Messaging.new(registry_url: "http://my-registry:8081/")
178
+ registry = AvroTurf::ConfluentSchemaRegistry.new("http://my-registry:8081/")
148
179
 
149
- # Returns true if the schema is compatible, false otherwise.
150
- avro.compatible?("person", schema)
180
+ # Returns true if the schema is compatible, nil if the subject or version is not registered, and false if incompatible.
181
+ registry.compatible?("person", schema)
182
+ ```
183
+
184
+ The ConfluentSchemaRegistry client can also change the global compatibility level or the compatibility level for an individual subject using the [Config API](http://docs.confluent.io/3.1.2/schema-registry/docs/api.html#config):
185
+
186
+ ```ruby
187
+ registry.update_global_config(compatibility: 'FULL')
188
+ registry.update_subject_config("person", compatibility: 'NONE')
151
189
  ```
152
190
 
153
191
  ### Testing Support
154
192
 
155
- AvroTurf includes a `FakeSchemaRegistryServer` that can be used in tests. The
193
+ AvroTurf includes a `FakeConfluentSchemaRegistryServer` that can be used in tests. The
156
194
  fake schema registry server depends on Sinatra but it is _not_ listed as a runtime
157
195
  dependency for AvroTurf. Sinatra must be added to your Gemfile or gemspec in order
158
196
  to use the fake server.
@@ -160,14 +198,14 @@ to use the fake server.
160
198
  Example using RSpec:
161
199
 
162
200
  ```ruby
163
- require 'avro_turf/test/fake_schema_registry_server'
201
+ require 'avro_turf/test/fake_confluent_schema_registry_server'
164
202
  require 'webmock/rspec'
165
203
 
166
204
  # within an example
167
205
  let(:registry_url) { "http://registry.example.com" }
168
206
  before do
169
- stub_request(:any, /^#{registry_url}/).to_rack(FakeSchemaRegistryServer)
170
- FakeSchemaRegistryServer.clear
207
+ stub_request(:any, /^#{registry_url}/).to_rack(FakeConfluentSchemaRegistryServer)
208
+ FakeConfluentSchemaRegistryServer.clear
171
209
  end
172
210
 
173
211
  # Messaging objects created with the same registry_url will now use the fake server.
@@ -17,14 +17,25 @@ Gem::Specification.new do |spec|
17
17
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
18
18
  spec.require_paths = ["lib"]
19
19
 
20
- spec.add_dependency "avro", ">= 1.7.7", "< 1.9"
20
+ spec.add_dependency "avro", ">= 1.7.7", "< 1.10"
21
21
  spec.add_dependency "excon", "~> 0.45"
22
22
 
23
- spec.add_development_dependency "bundler", "~> 1.7"
24
- spec.add_development_dependency "rake", "~> 10.0"
25
- spec.add_development_dependency "rspec", "~> 3.2.0"
26
- spec.add_development_dependency "fakefs", "~> 0.6.7"
23
+ spec.add_development_dependency "bundler", "~> 2.0"
24
+ spec.add_development_dependency "rake", "~> 13.0"
25
+ spec.add_development_dependency "rspec", "~> 3.2"
26
+ spec.add_development_dependency "fakefs", "~> 0.20.0"
27
27
  spec.add_development_dependency "webmock"
28
28
  spec.add_development_dependency "sinatra"
29
29
  spec.add_development_dependency "json_spec"
30
+ spec.add_development_dependency "rack-test"
31
+
32
+ spec.post_install_message = %{
33
+ avro_turf v0.8.0 deprecates the names AvroTurf::SchemaRegistry,
34
+ AvroTurf::CachedSchemaRegistry, and FakeSchemaRegistryServer.
35
+
36
+ Use AvroTurf::ConfluentSchemaRegistry, AvroTurf::CachedConfluentSchemaRegistry,
37
+ and FakeConfluentSchemaRegistryServer instead.
38
+
39
+ See https://github.com/dasch/avro_turf#deprecation-notice
40
+ }
30
41
  end
@@ -1,9 +1,18 @@
1
+ begin
2
+ require 'avro-patches'
3
+ rescue LoadError
4
+ false
5
+ end
1
6
  require 'avro_turf/version'
2
7
  require 'avro'
3
8
  require 'json'
4
9
  require 'avro_turf/schema_store'
5
10
  require 'avro_turf/core_ext'
6
- require 'avro_turf/schema_to_avro_patch'
11
+
12
+ # check for something that indicates Avro v1.9.0 or later
13
+ unless defined?(::Avro::LogicalTypes)
14
+ require 'avro_turf/schema_to_avro_patch'
15
+ end
7
16
 
8
17
  class AvroTurf
9
18
  class Error < StandardError; end
@@ -15,13 +24,15 @@ class AvroTurf
15
24
  # Create a new AvroTurf instance with the specified configuration.
16
25
  #
17
26
  # schemas_path - The String path to the root directory containing Avro schemas (default: "./schemas").
27
+ # schema_store - A schema store object that responds to #find(schema_name, namespace).
18
28
  # namespace - The String namespace that should be used to qualify schema names (optional).
19
29
  # codec - The String name of a codec that should be used to compress messages (optional).
20
30
  #
21
31
  # Currently, the only valid codec name is `deflate`.
22
- def initialize(schemas_path: nil, namespace: nil, codec: nil)
32
+ def initialize(schemas_path: nil, schema_store: nil, namespace: nil, codec: nil)
23
33
  @namespace = namespace
24
- @schema_store = SchemaStore.new(path: schemas_path || DEFAULT_SCHEMAS_PATH)
34
+ @schema_store = schema_store ||
35
+ SchemaStore.new(path: schemas_path || DEFAULT_SCHEMAS_PATH)
25
36
  @codec = codec
26
37
  end
27
38
 
@@ -0,0 +1,39 @@
1
+ require 'avro_turf/confluent_schema_registry'
2
+ require 'avro_turf/in_memory_cache'
3
+ require 'avro_turf/disk_cache'
4
+
5
+ # Caches registrations and lookups to the schema registry in memory.
6
+ class AvroTurf::CachedConfluentSchemaRegistry
7
+
8
+ # Instantiate a new CachedConfluentSchemaRegistry instance with the given configuration.
9
+ # By default, uses a provided InMemoryCache to prevent repeated calls to the upstream registry.
10
+ #
11
+ # upstream - The upstream schema registry object that fully responds to all methods in the
12
+ # AvroTurf::ConfluentSchemaRegistry interface.
13
+ # cache - Optional user provided Cache object that responds to all methods in the AvroTurf::InMemoryCache interface.
14
+ def initialize(upstream, cache: nil)
15
+ @upstream = upstream
16
+ @cache = cache || AvroTurf::InMemoryCache.new()
17
+ end
18
+
19
+ # Delegate the following methods to the upstream
20
+ %i(subjects subject_versions check compatible?
21
+ global_config update_global_config subject_config update_subject_config).each do |name|
22
+ define_method(name) do |*args|
23
+ instance_variable_get(:@upstream).send(name, *args)
24
+ end
25
+ end
26
+
27
+ def fetch(id)
28
+ @cache.lookup_by_id(id) || @cache.store_by_id(id, @upstream.fetch(id))
29
+ end
30
+
31
+ def register(subject, schema)
32
+ @cache.lookup_by_schema(subject, schema) || @cache.store_by_schema(subject, schema, @upstream.register(subject, schema))
33
+ end
34
+
35
+ def subject_version(subject, version = 'latest')
36
+ @cache.lookup_by_version(subject, version) ||
37
+ @cache.store_by_version(subject, version, @upstream.subject_version(subject, version))
38
+ end
39
+ end
@@ -1,26 +1,6 @@
1
- require 'avro_turf/schema_registry'
1
+ require 'avro_turf/cached_confluent_schema_registry'
2
2
 
3
- # Caches registrations and lookups to the schema registry in memory.
4
- class AvroTurf::CachedSchemaRegistry
3
+ # AvroTurf::CachedSchemaRegistry is deprecated and will be removed in a future release.
4
+ # Use AvroTurf::CachedConfluentSchemaRegistry instead.
5
5
 
6
- def initialize(upstream)
7
- @upstream = upstream
8
- @schemas_by_id = {}
9
- @ids_by_schema = {}
10
- end
11
-
12
- # Delegate the following methods to the upstream
13
- %i(subjects subject_versions subject_version check compatible?).each do |name|
14
- define_method(name) do |*args|
15
- instance_variable_get(:@upstream).send(name, *args)
16
- end
17
- end
18
-
19
- def fetch(id)
20
- @schemas_by_id[id] ||= @upstream.fetch(id)
21
- end
22
-
23
- def register(subject, schema)
24
- @ids_by_schema[subject + schema.to_s] ||= @upstream.register(subject, schema)
25
- end
26
- end
6
+ AvroTurf::CachedSchemaRegistry = AvroTurf::CachedConfluentSchemaRegistry
@@ -0,0 +1,125 @@
1
+ require 'excon'
2
+
3
+ class AvroTurf::ConfluentSchemaRegistry
4
+ CONTENT_TYPE = "application/vnd.schemaregistry.v1+json".freeze
5
+
6
+ def initialize(
7
+ url,
8
+ logger: Logger.new($stdout),
9
+ proxy: nil,
10
+ client_cert: nil,
11
+ client_key: nil,
12
+ client_key_pass: nil,
13
+ client_cert_data: nil,
14
+ client_key_data: nil
15
+ )
16
+ @logger = logger
17
+ headers = {
18
+ "Content-Type" => CONTENT_TYPE
19
+ }
20
+ headers[:proxy] = proxy if proxy&.present?
21
+ @connection = Excon.new(
22
+ url,
23
+ headers: headers,
24
+ client_cert: client_cert,
25
+ client_key: client_key,
26
+ client_key_pass: client_key_pass,
27
+ client_cert_data: client_cert_data,
28
+ client_key_data: client_key_data
29
+ )
30
+ end
31
+
32
+ def fetch(id)
33
+ @logger.info "Fetching schema with id #{id}"
34
+ data = get("/schemas/ids/#{id}")
35
+ data.fetch("schema")
36
+ end
37
+
38
+ def register(subject, schema)
39
+ data = post("/subjects/#{subject}/versions", body: {
40
+ schema: schema.to_s
41
+ }.to_json)
42
+
43
+ id = data.fetch("id")
44
+
45
+ @logger.info "Registered schema for subject `#{subject}`; id = #{id}"
46
+
47
+ id
48
+ end
49
+
50
+ # List all subjects
51
+ def subjects
52
+ get('/subjects')
53
+ end
54
+
55
+ # List all versions for a subject
56
+ def subject_versions(subject)
57
+ get("/subjects/#{subject}/versions")
58
+ end
59
+
60
+ # Get a specific version for a subject
61
+ def subject_version(subject, version = 'latest')
62
+ get("/subjects/#{subject}/versions/#{version}")
63
+ end
64
+
65
+ # Check if a schema exists. Returns nil if not found.
66
+ def check(subject, schema)
67
+ data = post("/subjects/#{subject}",
68
+ expects: [200, 404],
69
+ body: { schema: schema.to_s }.to_json)
70
+ data unless data.has_key?("error_code")
71
+ end
72
+
73
+ # Check if a schema is compatible with the stored version.
74
+ # Returns:
75
+ # - true if compatible
76
+ # - nil if the subject or version does not exist
77
+ # - false if incompatible
78
+ # http://docs.confluent.io/3.1.2/schema-registry/docs/api.html#compatibility
79
+ def compatible?(subject, schema, version = 'latest')
80
+ data = post("/compatibility/subjects/#{subject}/versions/#{version}",
81
+ expects: [200, 404],
82
+ body: { schema: schema.to_s }.to_json)
83
+ data.fetch('is_compatible', false) unless data.has_key?('error_code')
84
+ end
85
+
86
+ # Get global config
87
+ def global_config
88
+ get("/config")
89
+ end
90
+
91
+ # Update global config
92
+ def update_global_config(config)
93
+ put("/config", { body: config.to_json })
94
+ end
95
+
96
+ # Get config for subject
97
+ def subject_config(subject)
98
+ get("/config/#{subject}")
99
+ end
100
+
101
+ # Update config for subject
102
+ def update_subject_config(subject, config)
103
+ put("/config/#{subject}", { body: config.to_json })
104
+ end
105
+
106
+ private
107
+
108
+ def get(path, **options)
109
+ request(path, method: :get, **options)
110
+ end
111
+
112
+ def put(path, **options)
113
+ request(path, method: :put, **options)
114
+ end
115
+
116
+ def post(path, **options)
117
+ request(path, method: :post, **options)
118
+ end
119
+
120
+ def request(path, **options)
121
+ options = { expects: 200 }.merge!(options)
122
+ response = @connection.request(path: path, **options)
123
+ JSON.parse(response.body)
124
+ end
125
+ end