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 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