avro2kafka 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rspec +1 -0
- data/CHANGELOG.md +5 -1
- data/README.md +9 -3
- data/bin/avro2kafka +8 -3
- data/lib/avro2kafka.rb +28 -10
- data/lib/avro2kafka/kafka_publisher.rb +18 -12
- data/lib/avro2kafka/version.rb +1 -1
- data/spec/avro2kafka/kafka_publisher_spec.rb +28 -8
- data/spec/avro2kafka_spec.rb +45 -2
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d6e62da76fc477504898300a2a07e30d1c87d09e
|
4
|
+
data.tar.gz: a582a13d5231bfa9f2b56bc6033d706193573b08
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c6d08de11aca94e79abb24a179f752ea574ef772d7eea54a780cfa08f11ef9ba681431f4b932a1d72acc1dc110094f19586695f6d4446880dd45c8f76072e4a6
|
7
|
+
data.tar.gz: 6aded16c6bb9b00b215723629d5d10435cfc49f9277ad87dded43616099401b8bc5699b08173d7fa3bf3ca927d8a016291313aa21ca2d6db0949baecace21d59
|
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--color
|
data/CHANGELOG.md
CHANGED
@@ -3,12 +3,16 @@
|
|
3
3
|
All notable changes to this project are documented in this file.
|
4
4
|
This project adheres to [Semantic Versioning](http://semver.org/).
|
5
5
|
|
6
|
-
##
|
6
|
+
## 0.2.0 (2015-06-12)
|
7
7
|
|
8
8
|
### Added
|
9
9
|
|
10
|
+
* --data option for extra data to be added to the message payload
|
11
|
+
|
10
12
|
### Changed
|
11
13
|
|
14
|
+
* option --broker renamed to --broker-list and accpeting a comma separated list of brokers
|
15
|
+
|
12
16
|
## 0.1.0 (2015-06-09)
|
13
17
|
Initial release
|
14
18
|
|
data/README.md
CHANGED
@@ -26,14 +26,20 @@ Usage: avro2kafka [options] [file]
|
|
26
26
|
|
27
27
|
### Options
|
28
28
|
|
29
|
-
`-b, --broker BROKER`
|
30
|
-
|
29
|
+
`-b, --broker-list BROKER`
|
30
|
+
A comma-separated list of Kafka brokers, eg. localhost:9092. This value is required.
|
31
31
|
|
32
32
|
`-t, --topic TOPIC`
|
33
33
|
The Kafka topic to publish to. This value is required.
|
34
34
|
|
35
35
|
`-k, --key KEY`
|
36
|
-
|
36
|
+
A comma-separated list of fields in the avro that will act as keys for Kafka. If not supplied, unique and undefined keys will be generated, hence log compaction won't work.
|
37
|
+
|
38
|
+
`-d, --data DATA`
|
39
|
+
Extra data to pass along with the message payload. Format: key=value
|
40
|
+
Multiple arguments can be defined. For example:
|
41
|
+
|
42
|
+
$ avro2kafka [...] -d pwd=$PWD -d site=acme
|
37
43
|
|
38
44
|
`-h, --help`
|
39
45
|
Prints help
|
data/bin/avro2kafka
CHANGED
@@ -10,8 +10,8 @@ option_parser = OptionParser.new do |opts|
|
|
10
10
|
opts.banner = "Version #{Avro2Kafka::VERSION} of Avro2Kafka\n" \
|
11
11
|
"Usage: #{File.basename(__FILE__)} [options] [file]"
|
12
12
|
|
13
|
-
opts.on('-b', '--broker BROKER', 'The Kafka broker
|
14
|
-
options[:
|
13
|
+
opts.on('-b', '--broker-list BROKER', 'The Kafka broker list. Format: host:port. This value is required.') do |broker_list|
|
14
|
+
options[:broker_list] = broker_list
|
15
15
|
end
|
16
16
|
|
17
17
|
opts.on('-t', '--topic TOPIC', 'The Kafka topic to publish to. This value is required.') do |topic|
|
@@ -22,6 +22,11 @@ option_parser = OptionParser.new do |opts|
|
|
22
22
|
options[:key] = key
|
23
23
|
end
|
24
24
|
|
25
|
+
opts.on('-d', '--data DATA', 'Extra data fields to add to the kafka payload. Format: key=value') do |data|
|
26
|
+
options[:data] ||= []
|
27
|
+
options[:data] << data
|
28
|
+
end
|
29
|
+
|
25
30
|
opts.on('-h', '--help', 'Prints help') do
|
26
31
|
puts opts
|
27
32
|
exit
|
@@ -31,7 +36,7 @@ end
|
|
31
36
|
option_parser.parse!
|
32
37
|
|
33
38
|
begin
|
34
|
-
raise OptionParser::MissingArgument.new('--broker') if options[:
|
39
|
+
raise OptionParser::MissingArgument.new('--broker-list') if options[:broker_list].nil?
|
35
40
|
raise OptionParser::MissingArgument.new('--topic') if options[:topic].nil?
|
36
41
|
|
37
42
|
Avro2Kafka.new(options).publish
|
data/lib/avro2kafka.rb
CHANGED
@@ -3,26 +3,44 @@ require 'avro2kafka/avro_reader'
|
|
3
3
|
require 'avro2kafka/kafka_publisher'
|
4
4
|
|
5
5
|
class Avro2Kafka
|
6
|
-
attr_reader :
|
6
|
+
attr_reader :options
|
7
7
|
|
8
8
|
def initialize(options)
|
9
|
-
@input_path = ARGV.first
|
10
|
-
@kafka_broker = options.delete(:broker)
|
11
|
-
@kafka_topic = options.delete(:topic)
|
12
|
-
@kafka_key = options[:key]
|
13
|
-
|
14
9
|
@options = options
|
15
10
|
end
|
16
11
|
|
17
12
|
def reader
|
18
|
-
ARGF.
|
19
|
-
ARGF
|
13
|
+
ARGF.tap { |argf| argf.rewind }
|
20
14
|
end
|
21
15
|
|
22
16
|
def publish
|
23
17
|
records = AvroReader.new(reader).read
|
24
|
-
KafkaPublisher.new(
|
25
|
-
puts "Avro file published to #{
|
18
|
+
KafkaPublisher.new(**kafka_options).publish(records)
|
19
|
+
puts "Avro file published to #{topic} topic on #{broker_list}!"
|
20
|
+
end
|
21
|
+
|
22
|
+
def topic
|
23
|
+
options.fetch(:topic)
|
24
|
+
end
|
25
|
+
|
26
|
+
def broker_list
|
27
|
+
options.fetch(:broker_list).split(',')
|
28
|
+
end
|
29
|
+
|
30
|
+
def extra_data
|
31
|
+
options.fetch(:data, []).each_with_object({}) do |data, hash|
|
32
|
+
key, value = data.split('=')
|
33
|
+
hash[key] = value
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def kafka_options
|
38
|
+
{
|
39
|
+
broker_list: broker_list,
|
40
|
+
topic: topic,
|
41
|
+
data: extra_data,
|
42
|
+
keys: options.fetch(:key, '').split(',').map(&:strip),
|
43
|
+
}
|
26
44
|
end
|
27
45
|
|
28
46
|
end
|
@@ -3,26 +3,32 @@ require 'json'
|
|
3
3
|
|
4
4
|
class Avro2Kafka
|
5
5
|
class KafkaPublisher
|
6
|
-
attr_reader :producer, :topic, :keys
|
6
|
+
attr_reader :producer, :topic, :keys, :data
|
7
7
|
|
8
|
-
def initialize(
|
9
|
-
@producer = Poseidon::Producer.new(
|
8
|
+
def initialize(broker_list:, topic:, keys: [], data: {})
|
9
|
+
@producer = Poseidon::Producer.new(broker_list, 'avro2kafka')
|
10
10
|
@topic = topic
|
11
|
-
@keys = keys
|
11
|
+
@keys = keys
|
12
|
+
@data = data
|
12
13
|
end
|
13
14
|
|
14
15
|
def publish(records)
|
15
16
|
records.each_slice(100) do |batch|
|
16
|
-
messages = batch.map
|
17
|
-
if keys
|
18
|
-
message_key = keys.map { |key| record[key] }.join
|
19
|
-
Poseidon::MessageToSend.new(topic, record.to_json, message_key)
|
20
|
-
else
|
21
|
-
Poseidon::MessageToSend.new(topic, record.to_json)
|
22
|
-
end
|
23
|
-
end
|
17
|
+
messages = batch.map { |record| prepare_record(record) }
|
24
18
|
producer.send_messages(messages)
|
25
19
|
end
|
26
20
|
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def prepare_record(record)
|
25
|
+
record = record.merge(data) if record.is_a?(Hash)
|
26
|
+
if keys.empty?
|
27
|
+
Poseidon::MessageToSend.new(topic, record.to_json)
|
28
|
+
else
|
29
|
+
message_key = keys.map { |key| record[key] }.join
|
30
|
+
Poseidon::MessageToSend.new(topic, record.to_json, message_key)
|
31
|
+
end
|
32
|
+
end
|
27
33
|
end
|
28
34
|
end
|
data/lib/avro2kafka/version.rb
CHANGED
@@ -3,17 +3,37 @@ require 'poseidon'
|
|
3
3
|
|
4
4
|
RSpec.describe Avro2Kafka::KafkaPublisher do
|
5
5
|
describe '#publish' do
|
6
|
-
let(:
|
6
|
+
let(:broker_list) { %w[localhost:9092] }
|
7
7
|
let(:topic) { 'feeds' }
|
8
|
-
let(:
|
9
|
-
let(:
|
8
|
+
let(:keys) { %w[name id] }
|
9
|
+
let(:data) { {'site' => 'acme' } }
|
10
|
+
let(:messages) { [{ 'id' => 1, 'name' => 'dresses' }, { 'id' => 2, 'name' => 'tops' }] }
|
11
|
+
let(:publisher) { Avro2Kafka::KafkaPublisher.new(broker_list: broker_list, topic: topic, keys: keys, data: data) }
|
12
|
+
let(:producer) { double('producer') }
|
13
|
+
|
14
|
+
before do
|
15
|
+
allow(publisher).to receive(:producer).and_return(producer)
|
16
|
+
end
|
10
17
|
|
11
18
|
it 'should call publisher#send_messages' do
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
19
|
+
expect(producer).to receive(:send_messages).with(all(be_kind_of(Poseidon::MessageToSend)))
|
20
|
+
publisher.publish(messages)
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'should create keys' do
|
24
|
+
expect(producer).to receive(:send_messages) do |args|
|
25
|
+
expect(args).to all(have_attributes(key: a_value))
|
26
|
+
end
|
27
|
+
|
28
|
+
publisher.publish(messages)
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'should add extra data to payload' do
|
32
|
+
expect(producer).to receive(:send_messages) do |args|
|
33
|
+
expect(args.map { |a| JSON.load(a.value) }).to all(include({ 'site' => 'acme' }))
|
34
|
+
end
|
35
|
+
|
36
|
+
publisher.publish(messages)
|
17
37
|
end
|
18
38
|
end
|
19
39
|
end
|
data/spec/avro2kafka_spec.rb
CHANGED
@@ -6,15 +6,58 @@ RSpec.describe Avro2Kafka do
|
|
6
6
|
describe '#publish' do
|
7
7
|
let(:options) do
|
8
8
|
{
|
9
|
-
|
9
|
+
broker_list: 'localhost:9092,localhost:9093',
|
10
10
|
topic: 'feeds',
|
11
|
-
key: 'name,
|
11
|
+
key: 'name,id',
|
12
|
+
data: %w[x=y alpha=omega]
|
12
13
|
}
|
13
14
|
end
|
15
|
+
let(:avro2kafka) { Avro2Kafka.new(options) }
|
14
16
|
|
15
17
|
before do
|
16
18
|
ARGV.replace ['./spec/support/data.avro']
|
17
19
|
end
|
18
20
|
|
21
|
+
describe '#topic' do
|
22
|
+
it 'returns the topic' do
|
23
|
+
expect(avro2kafka.topic).to eq 'feeds'
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'throws an error if topic is not present' do
|
27
|
+
options.delete(:topic)
|
28
|
+
expect { avro2kafka.topic }.to raise_error
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
describe '#broker_list' do
|
33
|
+
it 'splits the broker list by commas' do
|
34
|
+
expect(avro2kafka.broker_list).to eq %w[localhost:9092 localhost:9093]
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'throws an error if broker_list is not present' do
|
38
|
+
options.delete(:broker_list)
|
39
|
+
expect { avro2kafka.broker_list }.to raise_error
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
describe '#extra_data' do
|
44
|
+
it 'parses data into key-value pairs' do
|
45
|
+
expect(avro2kafka.extra_data).to eq({ 'x' => 'y', 'alpha' => 'omega' })
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
describe '#publish' do
|
50
|
+
around do |ex|
|
51
|
+
stdout_old = $stdout
|
52
|
+
$stdout = File.open(File::NULL, 'w')
|
53
|
+
ex.run
|
54
|
+
$stdout = stdout_old
|
55
|
+
end
|
56
|
+
|
57
|
+
it 'forwards the records to KafkaPublisher#publish' do
|
58
|
+
expect_any_instance_of(Avro2Kafka::KafkaPublisher).to receive(:publish)
|
59
|
+
avro2kafka.publish
|
60
|
+
end
|
61
|
+
end
|
19
62
|
end
|
20
63
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: avro2kafka
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Peter Marton
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-06-
|
11
|
+
date: 2015-06-12 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -116,6 +116,7 @@ executables:
|
|
116
116
|
extensions: []
|
117
117
|
extra_rdoc_files: []
|
118
118
|
files:
|
119
|
+
- ".rspec"
|
119
120
|
- CHANGELOG.md
|
120
121
|
- Gemfile
|
121
122
|
- Gemfile.lock
|