fluent-plugin-kafka 0.19.6 → 0.19.8

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
  SHA256:
3
- metadata.gz: c01dcccf05f4c04f7dc5eb08c7f2da880ea7f892242931e95a009fe8cb18c270
4
- data.tar.gz: d446556bb8e8c65b7a2b16460aac3fab05720f444a5551bb7da486e5941e8c98
3
+ metadata.gz: f3e5a24c5a5c936b9d502631d6382faa9315a54f0813e5b636cf025ecb64212c
4
+ data.tar.gz: 43c1b088840c92c330c70c456d92e3849d9de326b6b772c55bae63affea9f3e3
5
5
  SHA512:
6
- metadata.gz: efe8546b9cdd28016b8872cdcbf91402b3cfd11b50ef1a63e38215b46128399bd05ed0d211cf03e8cbee1122b5316f15f95c3bc906937d2244340e4a9b68cdd8
7
- data.tar.gz: 100e538e93d736579a5886db60e3dc769807efc0ac516f352def62b126f7d636e594161ea72c490fffcf3c8a4f0bd22ada8fc59be40873aa407f9aa034e20e75
6
+ metadata.gz: 608800269cb2f306911e4a1a6a6aeea8b7e0e69fde4b34b5a165f67d4a306e82226de890151ffff813a9ba91ef4399d87aeb531243cb63cdf625a023cf92d3e7
7
+ data.tar.gz: e818390d3f8946d0449a80d8c8ddd612e4a96632b2d6765fdcbf3beeb394c0cdc7c95e0cdce8c0780a05e66e39812328ddb48cd32135476cf5a85dac244485ef
@@ -3,4 +3,21 @@ updates:
3
3
  - package-ecosystem: 'github-actions'
4
4
  directory: '/'
5
5
  schedule:
6
- interval: 'weekly'
6
+ interval: 'monthly'
7
+ groups:
8
+ # PR: "Security update [package] from [old] to [new]"
9
+ # This PR should be merged in hurry
10
+ security-updates:
11
+ applies-to: security-updates
12
+ patterns:
13
+ - '*'
14
+
15
+ # PR: "Bump [package] from [old] to [new]"
16
+ # No need to be merged this PR in hurry. It is enough to merge
17
+ # once in a month.
18
+ monthly-updates:
19
+ applies-to: version-updates
20
+ patterns:
21
+ - '*'
22
+ # Allow to create PR both of security and normal updates.
23
+ open-pull-requests-limit: 1
@@ -10,8 +10,15 @@ jobs:
10
10
  name: Add issue to project
11
11
  runs-on: ubuntu-latest
12
12
  steps:
13
- - uses: actions/add-to-project@v1.0.2
13
+ - name: Generate token
14
+ id: generate_token
15
+ uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0
16
+ with:
17
+ app-id: ${{ secrets.PROJECT_APP_ID }}
18
+ private-key: ${{ secrets.PROJECT_APP_PRIVATE_KEY }}
19
+ owner: fluent
20
+ - uses: actions/add-to-project@5afcf98fcd03f1c2f92c3c83f58ae24323cc57fd # v2.0.0
14
21
  with:
15
22
  project-url: https://github.com/orgs/fluent/projects/4
16
- github-token: ${{ secrets.ADD_TO_PROJECT_PAT }}
23
+ github-token: ${{ steps.generate_token.outputs.token }}
17
24
  labeled: waiting-for-triage
@@ -4,15 +4,23 @@ on:
4
4
  branches: [master]
5
5
  pull_request:
6
6
  branches: [master]
7
+ schedule:
8
+ - cron: '0 0 1 * *'
7
9
  jobs:
10
+ ruby-versions:
11
+ uses: ruby/actions/.github/workflows/ruby_versions.yml@master
12
+ with:
13
+ engine: cruby
14
+ min_version: 3.0
8
15
  build:
16
+ needs: ruby-versions
9
17
  runs-on: ${{ matrix.os }}
10
18
  env:
11
19
  USE_RDKAFKA: 1
12
20
  strategy:
13
21
  fail-fast: false
14
22
  matrix:
15
- ruby: [ '3.4', '3.3', '3.2', '3.1', '3.0' ]
23
+ ruby: ${{ fromJson(needs.ruby-versions.outputs.versions) }}
16
24
  os:
17
25
  - ubuntu-latest
18
26
  rdkafka_versions:
@@ -23,6 +31,7 @@ jobs:
23
31
  - { min: '>= 0.25.0', max: '>= 0.25.0' }
24
32
  # rdkafka 0.25.0 supports Ruby 3.2+, so we exclude it for Ruby 3.0 and 3.1
25
33
  exclude:
34
+ - ruby: head
26
35
  - ruby: '3.1'
27
36
  rdkafka_versions: { min: '>= 0.25.0', max: '>= 0.25.0' }
28
37
  - ruby: '3.0'
@@ -37,17 +46,17 @@ jobs:
37
46
  rdkafka_versions: { min: '>= 0.12.0', max: '= 0.15.2' }
38
47
  name: Ruby ${{ matrix.ruby }} unit testing on ${{ matrix.os }} with rdkafka gem version (min ${{ matrix.rdkafka_versions.min }} max ${{ matrix.rdkafka_versions.max }})
39
48
  steps:
40
- - uses: actions/checkout@v6
41
- - uses: ruby/setup-ruby@v1
49
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
50
+ - uses: ruby/setup-ruby@95ef2b042f9d7a56d8268cba8559e2842e2ad01b # v1.321.0
42
51
  with:
43
52
  ruby-version: ${{ matrix.ruby }}
44
53
  - name: Install confluent-kafka
45
54
  run: |
46
55
  sudo apt install -V -y gnupg2 wget
47
- wget https://packages.confluent.io/deb/6.0/archive.key
56
+ wget https://packages.confluent.io/deb/7.9/archive.key
48
57
  sudo gpg2 --homedir /tmp --no-default-keyring --keyring gnupg-ring:/usr/share/keyrings/confluent-archive-keyring.gpg --import archive.key
49
58
  sudo chmod 644 /usr/share/keyrings/confluent-archive-keyring.gpg
50
- sudo sh -c 'echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/confluent-archive-keyring.gpg] https://packages.confluent.io/deb/6.0 stable main" > /etc/apt/sources.list.d/confluent.list'
59
+ sudo sh -c 'echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/confluent-archive-keyring.gpg] https://packages.confluent.io/deb/7.9 stable main" > /etc/apt/sources.list.d/confluent.list'
51
60
  sudo apt update
52
61
  sudo apt install -y confluent-community-2.13 openjdk-11-jre netcat-openbsd
53
62
  - name: unit testing
@@ -57,6 +66,5 @@ jobs:
57
66
  RDKAFKA_VERSION_MAX_RANGE: ${{ matrix.rdkafka_versions.max }}
58
67
  run: |
59
68
  sudo ./ci/prepare-kafka-server.sh
60
- gem install rake
61
69
  bundle install --jobs 4 --retry 3
62
70
  bundle exec rake test
@@ -6,8 +6,11 @@ on:
6
6
  jobs:
7
7
  stale:
8
8
  runs-on: ubuntu-latest
9
+ permissions:
10
+ issues: write
11
+ pull-requests: write
9
12
  steps:
10
- - uses: actions/stale@v10
13
+ - uses: actions/stale@4391f3da665fdf50b6810c1a66712fb9ba21aa93 # v11.0.0
11
14
  with:
12
15
  repo-token: ${{ secrets.GITHUB_TOKEN }}
13
16
  days-before-stale: 90
data/ChangeLog CHANGED
@@ -1,3 +1,59 @@
1
+ Release 0.19.8 - 2026/09/04
2
+ * in_kafka: Emit each record with its own tag when `tag_source record` is
3
+ set. The whole fetch batch used to be emitted with the tag of the
4
+ last parsed record, so `tag_source record` never routed anything.
5
+ A record whose tag field is not a non-empty String is now skipped
6
+ with a warning, and the offset advances even when every message of
7
+ a batch is skipped. (#581)
8
+ * in_rdkafka_group: Apply SSL and SASL parameters to the consumer.
9
+ `ssl_ca_cert`, `ssl_client_cert`, `username`, `password`,
10
+ `scram_mechanism` and `principal` were accepted and then discarded,
11
+ so a consumer configured for SSL connected in the clear. NOTE: these
12
+ consumers start negotiating TLS after this change, and passing SASL
13
+ credentials through `kafka_configs` with `security.protocol
14
+ SASL_PLAINTEXT` is now rejected at startup unless `sasl_over_ssl
15
+ false` is set. (#580)
16
+ * out_rdkafka2: Apply `unrecoverable_error_codes` to delivery report
17
+ errors. The check only ran for errors raised by `produce`, so even the
18
+ default `msg_size_too_large` kept the chunk retrying until
19
+ `retry_timeout`. NOTE: such a chunk is now passed to the secondary
20
+ output, or written to the backup directory. (#579)
21
+ * out_rdkafka2: Fix the rdkafka patch version dispatch. The 0_16_0 patch
22
+ had been unreachable since it was added, and rdkafka-ruby 0.12.1
23
+ through 0.13.x got no patch at all, where `producer.close(10)` raises
24
+ ArgumentError on shutdown. Add a patch for 0.13.x. (#578)
25
+ * out_kafka2, out_rdkafka2: Skip an event whose `partition` is not a
26
+ decimal integer within the int32 range, and coerce `message_key` with
27
+ `to_s`. Such an event used to raise outside the per-event guard and
28
+ Fluentd retried the same chunk forever, stalling every source feeding
29
+ the output. NOTE: `partition` is now read as base 10, so "010" means
30
+ 10 rather than 8. (#576, #577)
31
+ * out_rdkafka2: Fix `scram_mechanism` being ignored. SCRAM was silently
32
+ downgraded to PLAIN. `scram_mechanism` is now an enum of the
33
+ mechanisms ruby-kafka accepts, so an unknown value is rejected at
34
+ startup instead of falling back. (#574, #575)
35
+ * out_rdkafka, out_rdkafka2: Fix `ssl_verify_hostname` being ignored, and
36
+ set the SSL flag for any of `ssl_ca_cert`, `ssl_ca_certs_from_system`,
37
+ `ssl_client_cert` and `ssl_client_cert_key`. A mutual TLS setup
38
+ without `ssl_ca_cert` used to connect in the clear. NOTE: these
39
+ configurations start negotiating TLS after this change. (#572, #573)
40
+ * Validate that `ssl_client_cert`, `ssl_client_cert_key`,
41
+ `ssl_client_cert_chain` and `ssl_client_cert_key_password` are set
42
+ together, in every plugin that accepts them. librdkafka used to accept
43
+ a certificate without a key and connect with no client identity.
44
+ NOTE: such a configuration is now rejected at startup. (#571)
45
+ * out_rdkafka2: Add `sasl_over_ssl` parameter and reject SASL credentials
46
+ over a plaintext connection. NOTE: a configuration that sends
47
+ `username` and `password` over `PLAINTEXT` or `SASL_PLAINTEXT` now
48
+ fails at startup. Set `sasl_over_ssl false` only if you deliberately
49
+ keep sending them in the clear. (#570)
50
+ * Replace yajl-ruby with the json gem for JSON handling, and keep
51
+ accepting duplicate keys so that json 3.0 does not raise
52
+ `JSON::ParserError`. (#566, #568)
53
+
54
+ Release 0.19.7 - 2026/06/02
55
+ * in_rdkafka_group: support regexp pattern in topics (#541)
56
+
1
57
  Release 0.19.6 - 2026/03/04
2
58
  * out_rdkafka2: Auto-configure SASL PLAIN when username and password are set (#547)
3
59
  * out_rdkafka2: Fix NoMethodError during graceful reload (#546)
data/README.md CHANGED
@@ -166,6 +166,12 @@ With the introduction of the rdkafka-ruby based input plugin we hope to support
166
166
  time_source <source for message timestamp (now|kafka|record)> :default => now
167
167
  time_format <string (Optional when use_record_time is used)>
168
168
 
169
+ # The SSL and SASL parameters in "Common parameters" are mapped to librdkafka
170
+ # properties. Keys given in kafka_configs take precedence over the mapped values.
171
+ # ssl_ca_cert uses only its first entry, and ssl_client_cert_chain is not mapped.
172
+ # When true, SASL authentication requires an SSL connection
173
+ sasl_over_ssl (bool) :default => true
174
+
169
175
  # kafka consumer options
170
176
  max_wait_time_ms 500
171
177
  max_batch_size 10000
@@ -177,6 +183,10 @@ With the introduction of the rdkafka-ruby based input plugin we hope to support
177
183
 
178
184
  See also [rdkafka-ruby](https://github.com/appsignal/rdkafka-ruby) and [librdkafka](https://github.com/edenhill/librdkafka/blob/master/CONFIGURATION.md) for more detailed documentation about Kafka consumer options.
179
185
 
186
+ `topics` supports POSIX Extended Regular Expression pattern (not Ruby regex syntax) since v0.19.6. If you want to use regex pattern, use `/pattern/` like `/foo.*/`.
187
+
188
+ **Note**: A caret (`^`) is automatically added to the beginning of the pattern.
189
+
180
190
  Consuming topic name is used for event tag. So when the target topic name is `app_event`, the tag is `app_event`. If you want to modify tag, use `add_prefix` or `add_suffix` parameter. With `add_prefix kafka`, the tag is `kafka.app_event`.
181
191
 
182
192
  ### Output plugin
@@ -519,6 +529,8 @@ You need to install rdkafka gem.
519
529
  discard_kafka_delivery_failed (bool) :default => false (No discard)
520
530
  discard_kafka_delivery_failed_regex (regexp) :default => nil (No discard)
521
531
  use_event_time (bool) :default => false
532
+ # When true, SASL authentication requires an SSL connection
533
+ sasl_over_ssl (bool) :default => true
522
534
 
523
535
  # same with kafka2
524
536
  headers (hash) :default => {}
@@ -565,6 +577,11 @@ You need to install rdkafka gem.
565
577
  `rdkafka2` supports `discard_kafka_delivery_failed_regex` parameter:
566
578
  - `discard_kafka_delivery_failed_regex` - default: nil - discard the record where the Kafka::DeliveryFailed occurred and the emitted message matches the given regex pattern, such as `/unknown_topic/`.
567
579
 
580
+ `rdkafka2` supports `unrecoverable_error_codes` parameter:
581
+ - `unrecoverable_error_codes` - default: `["topic_authorization_failed", "msg_size_too_large"]` - librdkafka error codes to treat as unrecoverable errors. When a listed code is returned, `Fluent::UnrecoverableError` is raised, so Fluentd hands the whole chunk to the secondary output or to the backup directory instead of retrying it until `retry_timeout` expires. Both the produce call and the delivery report are covered, the latter only while `rdkafka_delivery_handle_poll_timeout` is not 0. Set the parameter to an empty value keeping retrying every error, as this plugin did before v0.19.x.
582
+ - A code is spelled the way `Rdkafka::RdkafkaError#code` reports it, that is the librdkafka name lowercased with the leading underscore removed, so `RD_KAFKA_RESP_ERR__UNKNOWN_PARTITION` is written as `unknown_partition`. A name that matches no error code is accepted and never matches anything.
583
+ - `discard_kafka_delivery_failed` and `discard_kafka_delivery_failed_regex` take precedence over this parameter. The regex is matched against the librdkafka message, such as `Broker: Message size too large (msg_size_too_large)`.
584
+
568
585
  If you use v0.12, use `rdkafka` instead.
569
586
 
570
587
  <match kafka.**>
@@ -1,5 +1,7 @@
1
1
  #!/bin/sh
2
2
 
3
+ set -x
4
+
3
5
  export KAFKA_OPTS=-Dzookeeper.4lw.commands.whitelist=ruok
4
6
  /usr/bin/zookeeper-server-start /etc/kafka/zookeeper.properties &
5
7
  N_POLLING=30
@@ -30,4 +32,14 @@ while true ; do
30
32
  exit 1
31
33
  fi
32
34
  done
33
- /usr/bin/kafka-topics --create --zookeeper localhost:2181 --replication-factor 1 --partitions 1 --topic test
35
+ # kafka-topics --version fails to connect zookeeper here.
36
+ case "$(java -cp "/usr/share/java/kafka/*" kafka.Kafka --version 2>/dev/null)" in
37
+ 7.*)
38
+ /usr/bin/kafka-topics --create --bootstrap-server localhost:9092 --replication-factor 1 --partitions 1 --topic test
39
+ ;;
40
+ *)
41
+ # Deprecated zookeeper
42
+ /usr/bin/kafka-topics --create --zookeeper localhost:2181 --replication-factor 1 --partitions 1 --topic test
43
+ ;;
44
+ esac
45
+
@@ -13,7 +13,7 @@ Gem::Specification.new do |gem|
13
13
  gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
14
14
  gem.name = "fluent-plugin-kafka"
15
15
  gem.require_paths = ["lib"]
16
- gem.version = '0.19.6'
16
+ gem.version = '0.19.8'
17
17
  gem.required_ruby_version = ">= 2.1.0"
18
18
 
19
19
  gem.add_dependency "fluentd", [">= 0.10.58", "< 2"]
@@ -143,9 +143,9 @@ class Fluent::KafkaInput < Fluent::Input
143
143
  r
144
144
  }
145
145
  rescue LoadError
146
- require 'yajl'
146
+ require 'json'
147
147
  Proc.new { |msg, te|
148
- r = Yajl::Parser.parse(msg.value)
148
+ r = JSON.parse(msg.value, allow_duplicate_key: true)
149
149
  add_offset_in_hash(r, te, msg.offset) if @add_offset_in_record
150
150
  r
151
151
  }
@@ -302,18 +302,24 @@ class Fluent::KafkaInput < Fluent::Input
302
302
 
303
303
  return if messages.size.zero?
304
304
 
305
- es = Fluent::MultiEventStream.new
306
- tag = @topic_entry.topic
307
- tag = @add_prefix + "." + tag if @add_prefix
308
- tag = tag + "." + @add_suffix if @add_suffix
305
+ event_streams = {}
306
+ topic_tag = @topic_entry.topic
307
+ topic_tag = @add_prefix + "." + topic_tag if @add_prefix
308
+ topic_tag = topic_tag + "." + @add_suffix if @add_suffix
309
309
 
310
310
  messages.each { |msg|
311
311
  begin
312
312
  record = @parser.call(msg, @topic_entry)
313
313
  if @tag_source == :record
314
314
  tag = record[@record_tag_key]
315
+ unless tag.is_a?(String) && !tag.empty?
316
+ $log.warn "skipped record with invalid tag in #{@topic_entry.topic}/#{@topic_entry.partition}", :key => @record_tag_key, :value => tag, :offset => msg.offset
317
+ next
318
+ end
315
319
  tag = @add_prefix + "." + tag if @add_prefix
316
320
  tag = tag + "." + @add_suffix if @add_suffix
321
+ else
322
+ tag = topic_tag
317
323
  end
318
324
  case @time_source
319
325
  when :kafka
@@ -332,7 +338,8 @@ class Fluent::KafkaInput < Fluent::Input
332
338
  if @kafka_message_key
333
339
  record[@kafka_message_key] = msg.key
334
340
  end
335
- es.add(record_time, record)
341
+ event_streams[tag] ||= Fluent::MultiEventStream.new
342
+ event_streams[tag].add(record_time, record)
336
343
  rescue => e
337
344
  $log.warn "parser error in #{@topic_entry.topic}/#{@topic_entry.partition}", :error => e.to_s, :value => msg.value, :offset => msg.offset
338
345
  $log.debug_backtrace
@@ -340,14 +347,14 @@ class Fluent::KafkaInput < Fluent::Input
340
347
  }
341
348
  offset = messages.last.offset + 1
342
349
 
343
- unless es.empty?
350
+ event_streams.each { |tag, es|
344
351
  @router.emit_stream(tag, es)
352
+ }
345
353
 
346
- if @offset_manager
347
- @offset_manager.save_offset(offset)
348
- end
349
- @next_offset = offset
354
+ if @offset_manager
355
+ @offset_manager.save_offset(offset)
350
356
  end
357
+ @next_offset = offset
351
358
  end
352
359
  end
353
360
 
@@ -160,8 +160,8 @@ class Fluent::KafkaGroupInput < Fluent::Input
160
160
  Oj.default_options = Fluent::DEFAULT_OJ_OPTIONS
161
161
  Proc.new { |msg| Oj.load(msg.value) }
162
162
  rescue LoadError
163
- require 'yajl'
164
- Proc.new { |msg| Yajl::Parser.parse(msg.value) }
163
+ require 'json'
164
+ Proc.new { |msg| JSON.parse(msg.value, allow_duplicate_key: true) }
165
165
  end
166
166
  when 'ltsv'
167
167
  require 'ltsv'
@@ -265,7 +265,7 @@ class Fluent::KafkaGroupInput < Fluent::Input
265
265
  end
266
266
 
267
267
  def process_batch_with_record_tag(batch)
268
- es = {}
268
+ es = {}
269
269
  batch.messages.each { |msg|
270
270
  begin
271
271
  record = @parser_proc.call(msg)
@@ -361,7 +361,7 @@ class Fluent::KafkaGroupInput < Fluent::Input
361
361
  if @tag_source == :record
362
362
  process_batch_with_record_tag(batch)
363
363
  else
364
- process_batch(batch)
364
+ process_batch(batch)
365
365
  end
366
366
  }
367
367
  rescue ForShutdown
@@ -56,6 +56,7 @@ class Fluent::Plugin::RdKafkaGroupInput < Fluent::Plugin::Input
56
56
 
57
57
  include Fluent::KafkaPluginUtil::SSLSettings
58
58
  include Fluent::KafkaPluginUtil::SaslSettings
59
+ include Fluent::KafkaPluginUtil::RdkafkaSecuritySettings
59
60
 
60
61
  class ForShutdown < StandardError
61
62
  end
@@ -76,13 +77,25 @@ class Fluent::Plugin::RdKafkaGroupInput < Fluent::Plugin::Input
76
77
  end
77
78
  config_array
78
79
  end
80
+ private :_config_to_array
81
+
82
+ def regex_pattern?(topic)
83
+ topic.start_with?('/') && topic.end_with?('/') ? true : false
84
+ end
85
+ private :regex_pattern?
86
+
87
+ def _config_regex_pattern(topic)
88
+ # librdkafka recognizes string as regex pattern if the topic name starts with '^'.
89
+ # https://github.com/confluentinc/librdkafka/blob/570c785e9e35812db8f50254bd2f7e0cf47def39/src/rdkafka.h#L4148
90
+ # https://github.com/confluentinc/librdkafka/blob/e1db7eaa517f0a6438bc846a9c49ede73b9ea211/src/rdkafka_topic.c#L2064
91
+ [topic[1..-2].prepend('^')]
92
+ end
93
+ private :_config_regex_pattern
79
94
 
80
95
  def multi_workers_ready?
81
96
  true
82
97
  end
83
98
 
84
- private :_config_to_array
85
-
86
99
  def configure(conf)
87
100
  compat_parameters_convert(conf, :parser)
88
101
 
@@ -93,7 +106,12 @@ class Fluent::Plugin::RdKafkaGroupInput < Fluent::Plugin::Input
93
106
  log.info "Will watch for topics #{@topics} at brokers " \
94
107
  "#{@kafka_configs["bootstrap.servers"]} and '#{@kafka_configs["group.id"]}' group"
95
108
 
96
- @topics = _config_to_array(@topics)
109
+ topics = @topics.strip
110
+ if regex_pattern?(topics)
111
+ @topics = _config_regex_pattern(topics)
112
+ else
113
+ @topics = _config_to_array(topics)
114
+ end
97
115
 
98
116
  parser_conf = conf.elements('parse').first
99
117
  unless parser_conf
@@ -109,6 +127,8 @@ class Fluent::Plugin::RdKafkaGroupInput < Fluent::Plugin::Input
109
127
  if @time_source == :record and @time_format
110
128
  @time_parser = Fluent::TimeParser.new(@time_format)
111
129
  end
130
+
131
+ @rdkafka_config = build_config
112
132
  end
113
133
 
114
134
  def setup_parser(parser_conf)
@@ -120,8 +140,8 @@ class Fluent::Plugin::RdKafkaGroupInput < Fluent::Plugin::Input
120
140
  Oj.default_options = Fluent::DEFAULT_OJ_OPTIONS
121
141
  Proc.new { |msg| Oj.load(msg.payload) }
122
142
  rescue LoadError
123
- require 'yajl'
124
- Proc.new { |msg| Yajl::Parser.parse(msg.payload) }
143
+ require 'json'
144
+ Proc.new { |msg| JSON.parse(msg.payload, allow_duplicate_key: true) }
125
145
  end
126
146
  when 'ltsv'
127
147
  require 'ltsv'
@@ -160,8 +180,17 @@ class Fluent::Plugin::RdKafkaGroupInput < Fluent::Plugin::Input
160
180
  super
161
181
  end
162
182
 
183
+ def build_config
184
+ config = rdkafka_security_config
185
+ @kafka_configs.each { |k, v|
186
+ config[k.to_sym] = v
187
+ }
188
+ validate_sasl_over_ssl(config)
189
+ config
190
+ end
191
+
163
192
  def setup_consumer
164
- consumer = Rdkafka::Config.new(@kafka_configs).consumer
193
+ consumer = Rdkafka::Config.new(@rdkafka_config).consumer
165
194
  consumer.subscribe(*@topics)
166
195
  consumer
167
196
  end
@@ -1,5 +1,24 @@
1
1
  module Fluent
2
2
  module KafkaPluginUtil
3
+ module PartitionSettings
4
+ # -1 is the unassigned partition; the Kafka protocol takes the index as int32
5
+ PARTITION_RANGE = -1..(2**31 - 1)
6
+
7
+ def coerce_partition(partition)
8
+ partition = case partition
9
+ when Integer
10
+ partition
11
+ when String
12
+ Integer(partition, 10)
13
+ else
14
+ raise TypeError, "partition must be an Integer or a decimal String, got #{partition.class}"
15
+ end
16
+ raise RangeError, "partition #{partition} is out of range" unless PARTITION_RANGE.cover?(partition)
17
+
18
+ partition
19
+ end
20
+ end
21
+
3
22
  module AwsIamSettings
4
23
  def self.included(klass)
5
24
  klass.instance_eval do
@@ -36,6 +55,20 @@ module Fluent
36
55
 
37
56
  DummyFormatter = Object.new
38
57
 
58
+ def configure(conf)
59
+ super
60
+
61
+ if @ssl_client_cert && !@ssl_client_cert_key
62
+ raise Fluent::ConfigError, "ssl_client_cert is set but ssl_client_cert_key is not. Please provide both."
63
+ elsif !@ssl_client_cert && @ssl_client_cert_key
64
+ raise Fluent::ConfigError, "ssl_client_cert_key is set but ssl_client_cert is not. Please provide both."
65
+ elsif @ssl_client_cert_chain && !@ssl_client_cert
66
+ raise Fluent::ConfigError, "ssl_client_cert_chain is set but ssl_client_cert is not. Please provide cert, key and chain."
67
+ elsif @ssl_client_cert_key_password && !@ssl_client_cert_key
68
+ raise Fluent::ConfigError, "ssl_client_cert_key_password is set but ssl_client_cert_key is not. Please provide both."
69
+ end
70
+ end
71
+
39
72
  def start
40
73
  super
41
74
 
@@ -75,10 +108,82 @@ module Fluent
75
108
  :desc => "a username when using PLAIN/SCRAM SASL authentication"
76
109
  config_param :password, :string, :default => nil, secret: true,
77
110
  :desc => "a password when using PLAIN/SCRAM SASL authentication"
78
- config_param :scram_mechanism, :string, :default => nil,
111
+ config_param :scram_mechanism, :enum, :list => [:sha256, :sha512], :default => nil,
79
112
  :desc => "if set, use SCRAM authentication with specified mechanism. When unset, default to PLAIN authentication"
80
113
  }
81
114
  end
115
+
116
+ def configure(conf)
117
+ super
118
+
119
+ @scram_mechanism = @scram_mechanism.to_s if @scram_mechanism
120
+ end
121
+ end
122
+
123
+ module RdkafkaSecuritySettings
124
+ SCRAM_MECHANISMS = {
125
+ "sha256" => "SCRAM-SHA-256",
126
+ "sha512" => "SCRAM-SHA-512",
127
+ }.freeze
128
+
129
+ def self.included(klass)
130
+ klass.instance_eval {
131
+ config_param :service_name, :string, :default => nil, :desc => 'Used for sasl.kerberos.service.name'
132
+ config_param :sasl_over_ssl, :bool, :default => true,
133
+ :desc => 'When true, SASL authentication requires an SSL connection'
134
+ }
135
+ end
136
+
137
+ def rdkafka_security_config
138
+ config = {}
139
+
140
+ if (@ssl_ca_cert && @ssl_ca_cert[0]) || @ssl_ca_certs_from_system || @ssl_client_cert || @ssl_client_cert_key
141
+ ssl = true
142
+ config[:"ssl.ca.location"] = @ssl_ca_cert[0] if @ssl_ca_cert && @ssl_ca_cert[0]
143
+ config[:"ssl.certificate.location"] = @ssl_client_cert if @ssl_client_cert
144
+ config[:"ssl.key.location"] = @ssl_client_cert_key if @ssl_client_cert_key
145
+ config[:"ssl.key.password"] = @ssl_client_cert_key_password if @ssl_client_cert_key_password
146
+ config[:"ssl.endpoint.identification.algorithm"] = @ssl_verify_hostname ? "https" : "none"
147
+ config[:"enable.ssl.certificate.verification"] = true
148
+ end
149
+
150
+ if @principal
151
+ sasl = true
152
+ config[:"sasl.mechanisms"] = "GSSAPI"
153
+ config[:"sasl.kerberos.principal"] = @principal
154
+ config[:"sasl.kerberos.service.name"] = @service_name if @service_name
155
+ config[:"sasl.kerberos.keytab"] = @keytab if @keytab
156
+ end
157
+
158
+ if @username && @password
159
+ sasl = true
160
+ config[:"sasl.mechanisms"] = SCRAM_MECHANISMS.fetch(@scram_mechanism, 'PLAIN')
161
+ elsif @scram_mechanism
162
+ log.warn "scram_mechanism is ignored because username and password are not set"
163
+ end
164
+
165
+ if ssl && sasl
166
+ security_protocol = "SASL_SSL"
167
+ elsif ssl && !sasl
168
+ security_protocol = "SSL"
169
+ elsif !ssl && sasl
170
+ security_protocol = "SASL_PLAINTEXT"
171
+ else
172
+ security_protocol = "PLAINTEXT"
173
+ end
174
+ config[:"security.protocol"] = security_protocol
175
+
176
+ config[:"sasl.username"] = @username if @username
177
+ config[:"sasl.password"] = @password if @password
178
+
179
+ config
180
+ end
181
+
182
+ def validate_sasl_over_ssl(config)
183
+ if @sasl_over_ssl && config[:"sasl.password"] && config[:"security.protocol"].to_s.upcase == "SASL_PLAINTEXT"
184
+ raise Fluent::ConfigError, "SASL authentication requires that SSL is configured. Set 'sasl_over_ssl false' to send SASL credentials over a plaintext connection"
185
+ end
186
+ end
82
187
  end
83
188
  end
84
189
  end
@@ -96,7 +96,7 @@ DESC
96
96
  @seed_brokers = []
97
97
  z = Zookeeper.new(@zookeeper)
98
98
  z.get_children(:path => @zookeeper_path)[:children].each do |id|
99
- broker = Yajl.load(z.get(:path => @zookeeper_path + "/#{id}")[:data])
99
+ broker = JSON.parse(z.get(:path => @zookeeper_path + "/#{id}")[:data], allow_duplicate_key: true)
100
100
  if @ssl_client_cert
101
101
  @seed_brokers.push(pickup_ssl_endpoint(broker))
102
102
  else
@@ -192,8 +192,8 @@ DESC
192
192
 
193
193
  def setup_formatter(conf)
194
194
  if @output_data_type == 'json'
195
- require 'yajl'
196
- Proc.new { |tag, time, record| Yajl::Encoder.encode(record) }
195
+ require 'json'
196
+ Proc.new { |tag, time, record| JSON.generate(record) }
197
197
  elsif @output_data_type == 'ltsv'
198
198
  require 'ltsv'
199
199
  Proc.new { |tag, time, record| LTSV.dump(record) }
@@ -98,6 +98,7 @@ DESC
98
98
  include Fluent::KafkaPluginUtil::AwsIamSettings
99
99
  include Fluent::KafkaPluginUtil::SSLSettings
100
100
  include Fluent::KafkaPluginUtil::SaslSettings
101
+ include Fluent::KafkaPluginUtil::PartitionSettings
101
102
 
102
103
  def initialize
103
104
  super
@@ -314,8 +315,8 @@ DESC
314
315
  Oj.default_options = Fluent::DEFAULT_OJ_OPTIONS
315
316
  Proc.new { |tag, time, record| Oj.dump(record) }
316
317
  rescue LoadError
317
- require 'yajl'
318
- Proc.new { |tag, time, record| Yajl::Encoder.encode(record) }
318
+ require 'json'
319
+ Proc.new { |tag, time, record| JSON.generate(record) }
319
320
  end
320
321
  when 'ltsv'
321
322
  require 'ltsv'
@@ -351,6 +352,7 @@ DESC
351
352
  partition_key = (@exclude_partition_key ? record.delete(@partition_key_key) : record[@partition_key_key]) || @default_partition_key
352
353
  partition = (@exclude_partition ? record.delete(@partition_key) : record[@partition_key]) || @default_partition
353
354
  message_key = (@exclude_message_key ? record.delete(@message_key_key) : record[@message_key_key]) || @default_message_key
355
+ partition = coerce_partition(partition) unless partition.nil?
354
356
 
355
357
  if mutate_headers
356
358
  headers = base_headers.clone
@@ -119,7 +119,7 @@ DESC
119
119
  @seed_brokers = []
120
120
  z = Zookeeper.new(@zookeeper)
121
121
  z.get_children(:path => @zookeeper_path)[:children].each do |id|
122
- broker = Yajl.load(z.get(:path => @zookeeper_path + "/#{id}")[:data])
122
+ broker = JSON.parse(z.get(:path => @zookeeper_path + "/#{id}")[:data], allow_duplicate_key: true)
123
123
  if @ssl_client_cert
124
124
  @seed_brokers.push(pickup_ssl_endpoint(broker))
125
125
  else
@@ -259,8 +259,8 @@ DESC
259
259
  Oj.default_options = Fluent::DEFAULT_OJ_OPTIONS
260
260
  Proc.new { |tag, time, record| Oj.dump(record) }
261
261
  rescue LoadError
262
- require 'yajl'
263
- Proc.new { |tag, time, record| Yajl::Encoder.encode(record) }
262
+ require 'json'
263
+ Proc.new { |tag, time, record| JSON.generate(record) }
264
264
  end
265
265
  elsif @output_data_type == 'ltsv'
266
266
  require 'ltsv'