kafka_rest_client 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 55eaec42836887d93b9460701cdca823c0f63f77
4
+ data.tar.gz: b6eeb11b3fa7c973c2b3d51fe00631f5b92b63d6
5
+ SHA512:
6
+ metadata.gz: d9af06c6d72c0284905b76883f4dfc338eae7c6d39bfdb54ffefede69cd8b6462b1ac761085fced8340d9a274b7f76a46ae0ded6d59c5979fe13b9a1f49fa456
7
+ data.tar.gz: 3ac7cd879e202aea4ef4010099f3816455cd796d9545b316768a54098905a57e8b30f915fdb50acaae0b1f02f9b264d9f48b7e3e49261e4c2c0b977927366c3c
data/.gitignore ADDED
@@ -0,0 +1,9 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --format documentation
2
+ --color
data/.rubocop.yml ADDED
@@ -0,0 +1,4 @@
1
+ Style/Documentation:
2
+ Enabled: false
3
+ Metrics/LineLength:
4
+ Enabled: false
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 2.0.0-p645
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in kafka_rest_client.gemspec
4
+ gemspec
data/Guardfile ADDED
@@ -0,0 +1,45 @@
1
+ # A sample Guardfile
2
+ # More info at https://github.com/guard/guard#readme
3
+
4
+ ## Uncomment and set this to only include directories you want to watch
5
+ # directories %w(app lib config test spec features) \
6
+ # .select{|d| Dir.exists?(d) ? d : UI.warning("Directory #{d} does not exist")}
7
+
8
+ ## Note: if you are using the `directories` clause above and you are not
9
+ ## watching the project directory ('.'), then you will want to move
10
+ ## the Guardfile to a watched dir and symlink it back, e.g.
11
+ #
12
+ # $ mkdir config
13
+ # $ mv Guardfile config/
14
+ # $ ln -s config/Guardfile .
15
+ #
16
+ # and, you'll have to watch "config/Guardfile" instead of "Guardfile"
17
+
18
+ # Note: The cmd option is now required due to the increasing number of ways
19
+ # rspec may be run, below are examples of the most common uses.
20
+ # * bundler: 'bundle exec rspec'
21
+ # * bundler binstubs: 'bin/rspec'
22
+ # * spring: 'bin/rspec' (This will use spring if running and you have
23
+ # installed the spring binstubs per the docs)
24
+ # * zeus: 'zeus rspec' (requires the server to be started separately)
25
+ # * 'just' rspec: 'rspec'
26
+
27
+ guard :rspec, cmd: 'bundle exec rspec' do
28
+ require 'guard/rspec/dsl'
29
+ dsl = Guard::RSpec::Dsl.new(self)
30
+
31
+ # RSpec files
32
+ rspec = dsl.rspec
33
+ watch(rspec.spec_helper) { rspec.spec_dir }
34
+ watch(rspec.spec_support) { rspec.spec_dir }
35
+ watch(rspec.spec_files)
36
+
37
+ # Ruby files
38
+ ruby = dsl.ruby
39
+ dsl.watch_spec_files_for(ruby.lib_files)
40
+ end
41
+
42
+ guard :rubocop do
43
+ watch(/.+\.rb$/)
44
+ watch(%r{(?:.+/)?\.rubocop\.yml$}) { |m| File.dirname(m[0]) }
45
+ end
data/README.md ADDED
@@ -0,0 +1,68 @@
1
+ # KafkaRestClient
2
+
3
+ A Ruby client to interact with [Kafka REST Proxy](http://docs.confluent.io/1.0.1/kafka-rest/docs/index.html)
4
+
5
+ **Current Version:** 0.1.0
6
+
7
+ **Supported Ruby versions:** 2.0, 2.1, 2.2
8
+
9
+ ## Installation
10
+
11
+ Add this line to your application's Gemfile:
12
+
13
+ ```ruby
14
+ gem 'kafka_rest_client', git: 'git@github.com:FundingCircle/kafka_rest_client.git', tag: 'v0.1.0'
15
+ ```
16
+
17
+ ## Usage
18
+
19
+ ### Producing events
20
+ ```ruby
21
+ require 'kafka_rest_client'
22
+
23
+ # Configure global settings
24
+ KafkaRestClient.configure do |config|
25
+ config.kafka_proxy_url = ENV['KAFKA_PROXY_URL']
26
+ config.schema_registry_url = ENV['SCHEMA_REGISTRY_URL']
27
+ config.timeout = 10
28
+ config.logger = Rails.logger
29
+ end
30
+
31
+ producer = KafkaRestClient::AvroProducer.new
32
+
33
+ # Produce a single event using the topic name and payload
34
+ producer.produce('ice-cream-melted', { temperature: 35, unit: 'celsius' })
35
+
36
+ # Produce multiple events
37
+ producer.produce('ice-cream-melted', [{ temperature: 35, unit: 'celsius' }])
38
+
39
+ # Produce event objects by relying on #to_json and #as_json when using Rails ActiveSupport
40
+ class IceCreamMeltedEvent
41
+ attr_reader :temperature, :unit
42
+ def initialize(temperature, unit)
43
+ @temperature = temperature
44
+ @unit = unit
45
+ end
46
+
47
+ def as_json(_options = nil)
48
+ { temperature: temperature, unit: unit }
49
+ end
50
+ end
51
+
52
+ producer.produce('ice-cream-melted', [IceCreamMeltedEvent.new(35, 'celsius')])
53
+
54
+ # Exception handling
55
+ begin
56
+ producer.produce('ice-cream-melted', { temperature: 35, unit: 'celsius' })
57
+ rescue KafkaRestClient::SchemaNotFoundError => e
58
+ # Schema has not been registered in the schema registry
59
+ rescue KafkaRestClient::TopicNotFoundError => e
60
+ # Topic does not exist in Kafka, create it with confluent tools
61
+ rescue KafkaRestClient::Error => e
62
+ # Rescue any error raised by the library
63
+ end
64
+ ```
65
+
66
+ ## Contributing
67
+
68
+ Bug reports and pull requests are welcome on GitHub at https://github.com/fundingcircle/kafka_rest_client.
data/Rakefile ADDED
@@ -0,0 +1,8 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rspec/core/rake_task'
3
+ require 'rubocop/rake_task'
4
+
5
+ RSpec::Core::RakeTask.new(:spec)
6
+ RuboCop::RakeTask.new
7
+
8
+ task default: [:spec, :rubocop]
data/bin/console ADDED
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'bundler/setup'
4
+ require 'kafka_rest_client'
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
data/bin/setup ADDED
@@ -0,0 +1,7 @@
1
+ #!/bin/bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+
5
+ bundle install
6
+
7
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,30 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = 'kafka_rest_client'
7
+ spec.version = '0.1.0'
8
+ spec.authors = ['Funding Circle Engineering']
9
+ spec.email = ['engineering+kafka_rest_client@fundingcircle.com']
10
+
11
+ spec.summary = 'Ruby client for interacting with Kafka REST Proxy'
12
+ spec.description = 'Ruby client for interacting with Kafka REST Proxy'
13
+ spec.homepage = 'http://github.com/FundingCircle/kakfa_rest_client'
14
+ spec.files = `git ls-files -z`.split("\x0").reject { |f|
15
+ f.match(%r{^(test|spec|features)/})
16
+ }
17
+ spec.bindir = 'exe'
18
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
19
+ spec.require_paths = ['lib']
20
+
21
+ spec.add_development_dependency 'bundler', '~> 1.10'
22
+ spec.add_development_dependency 'rake', '~> 10.0'
23
+ spec.add_development_dependency 'rspec', '~> 3.4'
24
+ spec.add_development_dependency 'pry', '~> 0.10'
25
+ spec.add_development_dependency 'guard-rspec', '~> 4.6'
26
+ spec.add_development_dependency 'rubocop', '~> 0.36'
27
+ spec.add_development_dependency 'guard-rubocop', '~> 1.2'
28
+ spec.add_development_dependency 'webmock', '~> 1.22'
29
+ spec.add_dependency 'httparty', '~> 0.13'
30
+ end
@@ -0,0 +1,8 @@
1
+ require 'kafka_rest_client/avro_producer'
2
+
3
+ module KafkaRestClient
4
+ def self.configure
5
+ Configuration.current = Configuration.new
6
+ yield(Configuration.current)
7
+ end
8
+ end
@@ -0,0 +1,89 @@
1
+ require 'kafka_rest_client/errors'
2
+ require 'kafka_rest_client/configuration'
3
+ require 'httparty'
4
+ require 'json'
5
+
6
+ module KafkaRestClient
7
+ class AvroProducer
8
+ AVRO_AS_JSON = 'application/vnd.kafka.avro.v1+json'.freeze
9
+
10
+ attr_reader :config
11
+ private :config
12
+
13
+ def initialize(config = Configuration.current)
14
+ @config = config
15
+ end
16
+
17
+ def enabled?
18
+ if config.kafka_proxy_url && config.schema_registry_url
19
+ true
20
+ else
21
+ false
22
+ end
23
+ end
24
+
25
+ def produce(topic, events)
26
+ if enabled?
27
+ payload = build_event_payload(topic, Array(events))
28
+ response = post_event_to_kafka(topic, payload)
29
+ config.logger.debug("Produced to Kafka topic #{topic}: #{payload.inspect}")
30
+ response
31
+ end
32
+ end
33
+
34
+ private
35
+
36
+ def build_event_payload(topic, events)
37
+ {
38
+ value_schema_id: get_latest_schema(topic).fetch('id'),
39
+ records: events.map { |event| { value: event } }
40
+ }
41
+ end
42
+
43
+ def get_latest_schema(topic)
44
+ response = HTTParty.get(
45
+ "#{config.schema_registry_url}/subjects/#{topic}-value/versions/latest",
46
+ timeout: config.timeout
47
+ )
48
+
49
+ if response.code == 200
50
+ JSON.parse(response.body)
51
+ elsif response.code == 404
52
+ fail SchemaNotFoundError, "Schema for #{topic} not found: #{response.body}"
53
+ elsif response.code == 500
54
+ fail InternalServerError, "Schema registry internal error: #{response.body}"
55
+ elsif response.code == 503
56
+ fail BadGateway, "Bad gateway: #{response.body}"
57
+ else
58
+ fail "Unexpected error: #{response.body}"
59
+ end
60
+ end
61
+
62
+ def post_event_to_kafka(topic, message)
63
+ response = HTTParty.post(
64
+ "#{config.kafka_proxy_url}/topics/#{topic}",
65
+ headers: { 'Content-Type' => AVRO_AS_JSON },
66
+ body: message.to_json,
67
+ timeout: config.timeout
68
+ )
69
+
70
+ if response.code == 200
71
+ config.logger.debug("#{message[:records].count} event(s) published on #{topic}")
72
+ JSON.parse(response.body)
73
+ elsif response.code == 404
74
+ fail SchemaNotFoundError,
75
+ "Topic #{topic} not found in schema registry"
76
+ elsif response.code == 422
77
+ fail SchemaValidationError,
78
+ "Message #{message} does not match schema for #{topic} or is otherwise invalid"
79
+ elsif response.code == 500
80
+ fail InternalServerError,
81
+ 'Kafka Rest Proxy internal error (check zookeeper, kafka services)'
82
+ elsif response.code == 503
83
+ fail BadGateway, "Bad gateway: #{response.body}"
84
+ else
85
+ fail "Unexpected error: #{response.body}"
86
+ end
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,28 @@
1
+ require 'logger'
2
+
3
+ module KafkaRestClient
4
+ class Configuration
5
+ attr_accessor :kafka_proxy_url, :schema_registry_url, :timeout, :logger
6
+ class << self
7
+ attr_accessor :current
8
+ end
9
+
10
+ def initialize(options = {})
11
+ @kafka_proxy_url = options[:kafka_proxy_url]
12
+ @schema_registry_url = options[:schema_registry_url]
13
+ @timeout = options[:timeout]
14
+ @logger = options[:loggger]
15
+ end
16
+
17
+ def timeout
18
+ @timeout ||= 30
19
+ end
20
+
21
+ def logger
22
+ @logger ||= Logger.new(STDOUT).tap do |logger|
23
+ # Set level too high so we don't log anything
24
+ logger.level = Logger::FATAL
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,16 @@
1
+ module KafkaRestClient
2
+ # Base Error for all error classes of the library
3
+ class Error < StandardError; end
4
+
5
+ # Exception raised when the desired schema is not found (404 response code)
6
+ class SchemaNotFoundError < Error; end
7
+
8
+ # Exception raised on an internal server error (500 response code)
9
+ class InternalServerError < Error; end
10
+
11
+ # Exception raised on a message not matching the requested schema (422)
12
+ class SchemaValidationError < Error; end
13
+
14
+ # Exception raised on a bad gateway proxy resolution failure (503)
15
+ class BadGateway < Error; end
16
+ end
metadata ADDED
@@ -0,0 +1,184 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: kafka_rest_client
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Funding Circle Engineering
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2017-06-14 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.10'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.10'
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: pry
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '0.10'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '0.10'
69
+ - !ruby/object:Gem::Dependency
70
+ name: guard-rspec
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '4.6'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '4.6'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rubocop
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '0.36'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '0.36'
97
+ - !ruby/object:Gem::Dependency
98
+ name: guard-rubocop
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '1.2'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: '1.2'
111
+ - !ruby/object:Gem::Dependency
112
+ name: webmock
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - "~>"
116
+ - !ruby/object:Gem::Version
117
+ version: '1.22'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - "~>"
123
+ - !ruby/object:Gem::Version
124
+ version: '1.22'
125
+ - !ruby/object:Gem::Dependency
126
+ name: httparty
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - "~>"
130
+ - !ruby/object:Gem::Version
131
+ version: '0.13'
132
+ type: :runtime
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - "~>"
137
+ - !ruby/object:Gem::Version
138
+ version: '0.13'
139
+ description: Ruby client for interacting with Kafka REST Proxy
140
+ email:
141
+ - engineering+kafka_rest_client@fundingcircle.com
142
+ executables: []
143
+ extensions: []
144
+ extra_rdoc_files: []
145
+ files:
146
+ - ".gitignore"
147
+ - ".rspec"
148
+ - ".rubocop.yml"
149
+ - ".ruby-version"
150
+ - Gemfile
151
+ - Guardfile
152
+ - README.md
153
+ - Rakefile
154
+ - bin/console
155
+ - bin/setup
156
+ - kafka_rest_client.gemspec
157
+ - lib/kafka_rest_client.rb
158
+ - lib/kafka_rest_client/avro_producer.rb
159
+ - lib/kafka_rest_client/configuration.rb
160
+ - lib/kafka_rest_client/errors.rb
161
+ homepage: http://github.com/FundingCircle/kakfa_rest_client
162
+ licenses: []
163
+ metadata: {}
164
+ post_install_message:
165
+ rdoc_options: []
166
+ require_paths:
167
+ - lib
168
+ required_ruby_version: !ruby/object:Gem::Requirement
169
+ requirements:
170
+ - - ">="
171
+ - !ruby/object:Gem::Version
172
+ version: '0'
173
+ required_rubygems_version: !ruby/object:Gem::Requirement
174
+ requirements:
175
+ - - ">="
176
+ - !ruby/object:Gem::Version
177
+ version: '0'
178
+ requirements: []
179
+ rubyforge_project:
180
+ rubygems_version: 2.0.14
181
+ signing_key:
182
+ specification_version: 4
183
+ summary: Ruby client for interacting with Kafka REST Proxy
184
+ test_files: []