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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 442cbcdebce5cdbf488aff50c61ab23dafbece43
4
- data.tar.gz: 46a629b705531af0ada32f54987af81814772fd8
3
+ metadata.gz: d6e62da76fc477504898300a2a07e30d1c87d09e
4
+ data.tar.gz: a582a13d5231bfa9f2b56bc6033d706193573b08
5
5
  SHA512:
6
- metadata.gz: 6e9ec180f4fbe39e175fce8caa2dd97180f99faf35dfce6a50bde5b4fd4775380978b937602c57d6c3cb3cfe68fb301568de19a3c543eecb9d4d7b11b169531c
7
- data.tar.gz: b4ee9b33830dbfbd22b9ee8b7064263c9df79d8b149968353e69c8a70b7035086ca19661d447a5dbd5d161b3fedd7c026af88e0454b838b9611cbef822680a68
6
+ metadata.gz: c6d08de11aca94e79abb24a179f752ea574ef772d7eea54a780cfa08f11ef9ba681431f4b932a1d72acc1dc110094f19586695f6d4446880dd45c8f76072e4a6
7
+ data.tar.gz: 6aded16c6bb9b00b215723629d5d10435cfc49f9277ad87dded43616099401b8bc5699b08173d7fa3bf3ca927d8a016291313aa21ca2d6db0949baecace21d59
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color
@@ -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
- ## [Unreleased]
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
- The Kafka broker, eg. localhost:9092. This value is required.
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
- The 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.
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
@@ -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, eg. localhost:9092. This value is required.') do |broker|
14
- options[:broker] = broker
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[:broker].nil?
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
@@ -3,26 +3,44 @@ require 'avro2kafka/avro_reader'
3
3
  require 'avro2kafka/kafka_publisher'
4
4
 
5
5
  class Avro2Kafka
6
- attr_reader :input_path, :kafka_broker, :kafka_topic, :kafka_key
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.lineno = 0
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(kafka_broker, kafka_topic, kafka_key).publish(records)
25
- puts "Avro file published to #{kafka_topic} topic on #{kafka_broker}!"
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(broker, topic, keys)
9
- @producer = Poseidon::Producer.new([broker], "avro2kafka")
8
+ def initialize(broker_list:, topic:, keys: [], data: {})
9
+ @producer = Poseidon::Producer.new(broker_list, 'avro2kafka')
10
10
  @topic = topic
11
- @keys = keys.split(',').map(&:strip) if 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 do |record|
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
@@ -1,3 +1,3 @@
1
1
  class Avro2Kafka
2
- VERSION = "0.1.0"
2
+ VERSION = "0.2.0"
3
3
  end
@@ -3,17 +3,37 @@ require 'poseidon'
3
3
 
4
4
  RSpec.describe Avro2Kafka::KafkaPublisher do
5
5
  describe '#publish' do
6
- let(:broker) { 'localhost:9092' }
6
+ let(:broker_list) { %w[localhost:9092] }
7
7
  let(:topic) { 'feeds' }
8
- let(:key) { 'name, id' }
9
- let(:messages) { %w[message1, message2, message3] }
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
- kafka_publisher = Avro2Kafka::KafkaPublisher.new(broker, topic, key)
13
- mock_producer = double("Producer")
14
- allow(kafka_publisher).to receive(:producer).and_return(mock_producer)
15
- expect(mock_producer).to receive(:send_messages).with(all(be_kind_of(Poseidon::MessageToSend)))
16
- kafka_publisher.publish(messages)
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
@@ -6,15 +6,58 @@ RSpec.describe Avro2Kafka do
6
6
  describe '#publish' do
7
7
  let(:options) do
8
8
  {
9
- broker: 'localhost:9092',
9
+ broker_list: 'localhost:9092,localhost:9093',
10
10
  topic: 'feeds',
11
- key: 'name, id'
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.1.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 00:00:00.000000000 Z
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