avro2kafka 0.1.0 → 0.2.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 +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
|