ruby-kafka 0.7.10 → 1.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. checksums.yaml +4 -4
  2. data/.circleci/config.yml +179 -0
  3. data/.github/workflows/stale.yml +19 -0
  4. data/.ruby-version +1 -1
  5. data/CHANGELOG.md +40 -0
  6. data/README.md +167 -0
  7. data/lib/kafka/async_producer.rb +60 -42
  8. data/lib/kafka/client.rb +92 -6
  9. data/lib/kafka/cluster.rb +82 -24
  10. data/lib/kafka/connection.rb +3 -0
  11. data/lib/kafka/consumer.rb +61 -11
  12. data/lib/kafka/consumer_group/assignor.rb +63 -0
  13. data/lib/kafka/consumer_group.rb +29 -6
  14. data/lib/kafka/crc32_hash.rb +15 -0
  15. data/lib/kafka/datadog.rb +20 -13
  16. data/lib/kafka/digest.rb +22 -0
  17. data/lib/kafka/fetcher.rb +5 -2
  18. data/lib/kafka/interceptors.rb +33 -0
  19. data/lib/kafka/murmur2_hash.rb +17 -0
  20. data/lib/kafka/offset_manager.rb +12 -1
  21. data/lib/kafka/partitioner.rb +8 -3
  22. data/lib/kafka/producer.rb +13 -5
  23. data/lib/kafka/prometheus.rb +78 -79
  24. data/lib/kafka/protocol/add_offsets_to_txn_response.rb +2 -0
  25. data/lib/kafka/protocol/encoder.rb +1 -1
  26. data/lib/kafka/protocol/join_group_request.rb +8 -2
  27. data/lib/kafka/protocol/join_group_response.rb +9 -1
  28. data/lib/kafka/protocol/metadata_response.rb +1 -1
  29. data/lib/kafka/protocol/offset_fetch_request.rb +3 -1
  30. data/lib/kafka/protocol/record_batch.rb +2 -2
  31. data/lib/kafka/protocol/sasl_handshake_request.rb +1 -1
  32. data/lib/kafka/protocol/sync_group_response.rb +5 -2
  33. data/lib/kafka/protocol/txn_offset_commit_response.rb +34 -5
  34. data/lib/kafka/round_robin_assignment_strategy.rb +37 -39
  35. data/lib/kafka/sasl/awsmskiam.rb +133 -0
  36. data/lib/kafka/sasl_authenticator.rb +15 -2
  37. data/lib/kafka/ssl_context.rb +6 -5
  38. data/lib/kafka/tagged_logger.rb +1 -0
  39. data/lib/kafka/transaction_manager.rb +30 -10
  40. data/lib/kafka/version.rb +1 -1
  41. data/ruby-kafka.gemspec +5 -4
  42. metadata +39 -13
@@ -0,0 +1,133 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'securerandom'
4
+ require 'base64'
5
+ require 'json'
6
+
7
+ module Kafka
8
+ module Sasl
9
+ class AwsMskIam
10
+ AWS_MSK_IAM = "AWS_MSK_IAM"
11
+
12
+ def initialize(aws_region:, access_key_id:, secret_key_id:, session_token: nil, logger:)
13
+ @semaphore = Mutex.new
14
+
15
+ @aws_region = aws_region
16
+ @access_key_id = access_key_id
17
+ @secret_key_id = secret_key_id
18
+ @session_token = session_token
19
+ @logger = TaggedLogger.new(logger)
20
+ end
21
+
22
+ def ident
23
+ AWS_MSK_IAM
24
+ end
25
+
26
+ def configured?
27
+ @aws_region && @access_key_id && @secret_key_id
28
+ end
29
+
30
+ def authenticate!(host, encoder, decoder)
31
+ @logger.debug "Authenticating #{@access_key_id} with SASL #{AWS_MSK_IAM}"
32
+
33
+ host_without_port = host.split(':', -1).first
34
+
35
+ time_now = Time.now.utc
36
+
37
+ msg = authentication_payload(host: host_without_port, time_now: time_now)
38
+ @logger.debug "Sending first client SASL AWS_MSK_IAM message:"
39
+ @logger.debug msg
40
+ encoder.write_bytes(msg)
41
+
42
+ begin
43
+ @server_first_message = decoder.bytes
44
+ @logger.debug "Received first server SASL AWS_MSK_IAM message: #{@server_first_message}"
45
+
46
+ raise Kafka::Error, "SASL AWS_MSK_IAM authentication failed: unknown error" unless @server_first_message
47
+ rescue Errno::ETIMEDOUT, EOFError => e
48
+ raise Kafka::Error, "SASL AWS_MSK_IAM authentication failed: #{e.message}"
49
+ end
50
+
51
+ @logger.debug "SASL #{AWS_MSK_IAM} authentication successful"
52
+ end
53
+
54
+ private
55
+
56
+ def bin_to_hex(s)
57
+ s.each_byte.map { |b| b.to_s(16).rjust(2, '0') }.join
58
+ end
59
+
60
+ def digest
61
+ @digest ||= OpenSSL::Digest::SHA256.new
62
+ end
63
+
64
+ def authentication_payload(host:, time_now:)
65
+ {
66
+ 'version' => "2020_10_22",
67
+ 'host' => host,
68
+ 'user-agent' => "ruby-kafka",
69
+ 'action' => "kafka-cluster:Connect",
70
+ 'x-amz-algorithm' => "AWS4-HMAC-SHA256",
71
+ 'x-amz-credential' => @access_key_id + "/" + time_now.strftime("%Y%m%d") + "/" + @aws_region + "/kafka-cluster/aws4_request",
72
+ 'x-amz-date' => time_now.strftime("%Y%m%dT%H%M%SZ"),
73
+ 'x-amz-signedheaders' => "host",
74
+ 'x-amz-expires' => "900",
75
+ 'x-amz-security-token' => @session_token,
76
+ 'x-amz-signature' => signature(host: host, time_now: time_now)
77
+ }.delete_if { |_, v| v.nil? }.to_json
78
+ end
79
+
80
+ def canonical_request(host:, time_now:)
81
+ "GET\n" +
82
+ "/\n" +
83
+ canonical_query_string(time_now: time_now) + "\n" +
84
+ canonical_headers(host: host) + "\n" +
85
+ signed_headers + "\n" +
86
+ hashed_payload
87
+ end
88
+
89
+ def canonical_query_string(time_now:)
90
+ params = {
91
+ "Action" => "kafka-cluster:Connect",
92
+ "X-Amz-Algorithm" => "AWS4-HMAC-SHA256",
93
+ "X-Amz-Credential" => @access_key_id + "/" + time_now.strftime("%Y%m%d") + "/" + @aws_region + "/kafka-cluster/aws4_request",
94
+ "X-Amz-Date" => time_now.strftime("%Y%m%dT%H%M%SZ"),
95
+ "X-Amz-Expires" => "900",
96
+ "X-Amz-Security-Token" => @session_token,
97
+ "X-Amz-SignedHeaders" => "host"
98
+ }.delete_if { |_, v| v.nil? }
99
+
100
+ URI.encode_www_form(params)
101
+ end
102
+
103
+ def canonical_headers(host:)
104
+ "host" + ":" + host + "\n"
105
+ end
106
+
107
+ def signed_headers
108
+ "host"
109
+ end
110
+
111
+ def hashed_payload
112
+ bin_to_hex(digest.digest(""))
113
+ end
114
+
115
+ def string_to_sign(host:, time_now:)
116
+ "AWS4-HMAC-SHA256" + "\n" +
117
+ time_now.strftime("%Y%m%dT%H%M%SZ") + "\n" +
118
+ time_now.strftime("%Y%m%d") + "/" + @aws_region + "/kafka-cluster/aws4_request" + "\n" +
119
+ bin_to_hex(digest.digest(canonical_request(host: host, time_now: time_now)))
120
+ end
121
+
122
+ def signature(host:, time_now:)
123
+ date_key = OpenSSL::HMAC.digest("SHA256", "AWS4" + @secret_key_id, time_now.strftime("%Y%m%d"))
124
+ date_region_key = OpenSSL::HMAC.digest("SHA256", date_key, @aws_region)
125
+ date_region_service_key = OpenSSL::HMAC.digest("SHA256", date_region_key, "kafka-cluster")
126
+ signing_key = OpenSSL::HMAC.digest("SHA256", date_region_service_key, "aws4_request")
127
+ signature = bin_to_hex(OpenSSL::HMAC.digest("SHA256", signing_key, string_to_sign(host: host, time_now: time_now)))
128
+
129
+ signature
130
+ end
131
+ end
132
+ end
133
+ end
@@ -4,13 +4,18 @@ require 'kafka/sasl/plain'
4
4
  require 'kafka/sasl/gssapi'
5
5
  require 'kafka/sasl/scram'
6
6
  require 'kafka/sasl/oauth'
7
+ require 'kafka/sasl/awsmskiam'
7
8
 
8
9
  module Kafka
9
10
  class SaslAuthenticator
10
11
  def initialize(logger:, sasl_gssapi_principal:, sasl_gssapi_keytab:,
11
12
  sasl_plain_authzid:, sasl_plain_username:, sasl_plain_password:,
12
13
  sasl_scram_username:, sasl_scram_password:, sasl_scram_mechanism:,
13
- sasl_oauth_token_provider:)
14
+ sasl_oauth_token_provider:,
15
+ sasl_aws_msk_iam_access_key_id:,
16
+ sasl_aws_msk_iam_secret_key_id:,
17
+ sasl_aws_msk_iam_aws_region:,
18
+ sasl_aws_msk_iam_session_token: nil)
14
19
  @logger = TaggedLogger.new(logger)
15
20
 
16
21
  @plain = Sasl::Plain.new(
@@ -33,12 +38,20 @@ module Kafka
33
38
  logger: @logger,
34
39
  )
35
40
 
41
+ @aws_msk_iam = Sasl::AwsMskIam.new(
42
+ access_key_id: sasl_aws_msk_iam_access_key_id,
43
+ secret_key_id: sasl_aws_msk_iam_secret_key_id,
44
+ aws_region: sasl_aws_msk_iam_aws_region,
45
+ session_token: sasl_aws_msk_iam_session_token,
46
+ logger: @logger,
47
+ )
48
+
36
49
  @oauth = Sasl::OAuth.new(
37
50
  token_provider: sasl_oauth_token_provider,
38
51
  logger: @logger,
39
52
  )
40
53
 
41
- @mechanism = [@gssapi, @plain, @scram, @oauth].find(&:configured?)
54
+ @mechanism = [@gssapi, @plain, @scram, @oauth, @aws_msk_iam].find(&:configured?)
42
55
  end
43
56
 
44
57
  def enabled?
@@ -47,18 +47,19 @@ module Kafka
47
47
  Array(ca_cert).each do |cert|
48
48
  store.add_cert(OpenSSL::X509::Certificate.new(cert))
49
49
  end
50
- if ca_cert_file_path
51
- store.add_file(ca_cert_file_path)
50
+ Array(ca_cert_file_path).each do |cert_file_path|
51
+ store.add_file(cert_file_path)
52
52
  end
53
53
  if ca_certs_from_system
54
54
  store.set_default_paths
55
55
  end
56
56
  ssl_context.cert_store = store
57
- ssl_context.verify_mode = OpenSSL::SSL::VERIFY_PEER
58
- # Verify certificate hostname if supported (ruby >= 2.4.0)
59
- ssl_context.verify_hostname = verify_hostname if ssl_context.respond_to?(:verify_hostname=)
60
57
  end
61
58
 
59
+ ssl_context.verify_mode = OpenSSL::SSL::VERIFY_PEER
60
+ # Verify certificate hostname if supported (ruby >= 2.4.0)
61
+ ssl_context.verify_hostname = verify_hostname if ssl_context.respond_to?(:verify_hostname=)
62
+
62
63
  ssl_context
63
64
  end
64
65
  end
@@ -1,6 +1,7 @@
1
1
  # Basic implementation of a tagged logger that matches the API of
2
2
  # ActiveSupport::TaggedLogging.
3
3
 
4
+ require 'delegate'
4
5
  require 'logger'
5
6
 
6
7
  module Kafka
@@ -95,7 +95,7 @@ module Kafka
95
95
  force_transactional!
96
96
 
97
97
  if @transaction_state.uninitialized?
98
- raise 'Transaction is uninitialized'
98
+ raise Kafka::InvalidTxnStateError, 'Transaction is uninitialized'
99
99
  end
100
100
 
101
101
  # Extract newly created partitions
@@ -138,8 +138,8 @@ module Kafka
138
138
 
139
139
  def begin_transaction
140
140
  force_transactional!
141
- raise 'Transaction has already started' if @transaction_state.in_transaction?
142
- raise 'Transaction is not ready' unless @transaction_state.ready?
141
+ raise Kafka::InvalidTxnStateError, 'Transaction has already started' if @transaction_state.in_transaction?
142
+ raise Kafka::InvalidTxnStateError, 'Transaction is not ready' unless @transaction_state.ready?
143
143
  @transaction_state.transition_to!(TransactionStateMachine::IN_TRANSACTION)
144
144
 
145
145
  @logger.info "Begin transaction #{@transactional_id}, Producer ID: #{@producer_id} (Epoch #{@producer_epoch})"
@@ -159,7 +159,7 @@ module Kafka
159
159
  end
160
160
 
161
161
  unless @transaction_state.in_transaction?
162
- raise 'Transaction is not valid to commit'
162
+ raise Kafka::InvalidTxnStateError, 'Transaction is not valid to commit'
163
163
  end
164
164
 
165
165
  @transaction_state.transition_to!(TransactionStateMachine::COMMITTING_TRANSACTION)
@@ -192,7 +192,8 @@ module Kafka
192
192
  end
193
193
 
194
194
  unless @transaction_state.in_transaction?
195
- raise 'Transaction is not valid to abort'
195
+ @logger.warn('Aborting transaction that was never opened on brokers')
196
+ return
196
197
  end
197
198
 
198
199
  @transaction_state.transition_to!(TransactionStateMachine::ABORTING_TRANSACTION)
@@ -221,7 +222,7 @@ module Kafka
221
222
  force_transactional!
222
223
 
223
224
  unless @transaction_state.in_transaction?
224
- raise 'Transaction is not valid to send offsets'
225
+ raise Kafka::InvalidTxnStateError, 'Transaction is not valid to send offsets'
225
226
  end
226
227
 
227
228
  add_response = transaction_coordinator.add_offsets_to_txn(
@@ -232,14 +233,23 @@ module Kafka
232
233
  )
233
234
  Protocol.handle_error(add_response.error_code)
234
235
 
235
- send_response = transaction_coordinator.txn_offset_commit(
236
+ send_response = group_coordinator(group_id: group_id).txn_offset_commit(
236
237
  transactional_id: @transactional_id,
237
238
  group_id: group_id,
238
239
  producer_id: @producer_id,
239
240
  producer_epoch: @producer_epoch,
240
241
  offsets: offsets
241
242
  )
242
- Protocol.handle_error(send_response.error_code)
243
+ send_response.errors.each do |tp|
244
+ tp.partitions.each do |partition|
245
+ Protocol.handle_error(partition.error_code)
246
+ end
247
+ end
248
+
249
+ nil
250
+ rescue
251
+ @transaction_state.transition_to!(TransactionStateMachine::ERROR)
252
+ raise
243
253
  end
244
254
 
245
255
  def in_transaction?
@@ -250,6 +260,10 @@ module Kafka
250
260
  @transaction_state.error?
251
261
  end
252
262
 
263
+ def ready?
264
+ @transaction_state.ready?
265
+ end
266
+
253
267
  def close
254
268
  if in_transaction?
255
269
  @logger.warn("Aborting pending transaction ...")
@@ -264,11 +278,11 @@ module Kafka
264
278
 
265
279
  def force_transactional!
266
280
  unless transactional?
267
- raise 'Please turn on transactional mode to use transaction'
281
+ raise Kafka::InvalidTxnStateError, 'Please turn on transactional mode to use transaction'
268
282
  end
269
283
 
270
284
  if @transactional_id.nil? || @transactional_id.empty?
271
- raise 'Please provide a transaction_id to use transactional mode'
285
+ raise Kafka::InvalidTxnStateError, 'Please provide a transaction_id to use transactional mode'
272
286
  end
273
287
  end
274
288
 
@@ -278,6 +292,12 @@ module Kafka
278
292
  )
279
293
  end
280
294
 
295
+ def group_coordinator(group_id:)
296
+ @cluster.get_group_coordinator(
297
+ group_id: group_id
298
+ )
299
+ end
300
+
281
301
  def complete_transaction
282
302
  @transaction_state.transition_to!(TransactionStateMachine::READY)
283
303
  @transaction_partitions = {}
data/lib/kafka/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Kafka
4
- VERSION = "0.7.10"
4
+ VERSION = "1.5.0"
5
5
  end
data/ruby-kafka.gemspec CHANGED
@@ -18,7 +18,7 @@ Gem::Specification.new do |spec|
18
18
  DESC
19
19
 
20
20
  spec.homepage = "https://github.com/zendesk/ruby-kafka"
21
- spec.license = "Apache License Version 2.0"
21
+ spec.license = "Apache-2.0"
22
22
 
23
23
  spec.required_ruby_version = '>= 2.1.0'
24
24
 
@@ -33,18 +33,19 @@ Gem::Specification.new do |spec|
33
33
  spec.add_development_dependency "rake", "~> 10.0"
34
34
  spec.add_development_dependency "rspec"
35
35
  spec.add_development_dependency "pry"
36
+ spec.add_development_dependency "digest-murmurhash"
36
37
  spec.add_development_dependency "dotenv"
37
38
  spec.add_development_dependency "docker-api"
38
39
  spec.add_development_dependency "rspec-benchmark"
39
- spec.add_development_dependency "activesupport"
40
+ spec.add_development_dependency "activesupport", ">= 4.0", "< 6.1"
40
41
  spec.add_development_dependency "snappy"
41
42
  spec.add_development_dependency "extlz4"
42
43
  spec.add_development_dependency "zstd-ruby"
43
44
  spec.add_development_dependency "colored"
44
45
  spec.add_development_dependency "rspec_junit_formatter", "0.2.2"
45
- spec.add_development_dependency "dogstatsd-ruby", ">= 3.0.0", "< 5.0.0"
46
+ spec.add_development_dependency "dogstatsd-ruby", ">= 4.0.0", "< 5.0.0"
46
47
  spec.add_development_dependency "statsd-ruby"
47
- spec.add_development_dependency "prometheus-client"
48
+ spec.add_development_dependency "prometheus-client", "~> 0.10.0"
48
49
  spec.add_development_dependency "ruby-prof"
49
50
  spec.add_development_dependency "timecop"
50
51
  spec.add_development_dependency "rubocop", "~> 0.49.1"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruby-kafka
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.10
4
+ version: 1.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Daniel Schierbeck
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2019-08-07 00:00:00.000000000 Z
11
+ date: 2022-05-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: digest-crc
@@ -80,6 +80,20 @@ dependencies:
80
80
  - - ">="
81
81
  - !ruby/object:Gem::Version
82
82
  version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: digest-murmurhash
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
83
97
  - !ruby/object:Gem::Dependency
84
98
  name: dotenv
85
99
  requirement: !ruby/object:Gem::Requirement
@@ -128,14 +142,20 @@ dependencies:
128
142
  requirements:
129
143
  - - ">="
130
144
  - !ruby/object:Gem::Version
131
- version: '0'
145
+ version: '4.0'
146
+ - - "<"
147
+ - !ruby/object:Gem::Version
148
+ version: '6.1'
132
149
  type: :development
133
150
  prerelease: false
134
151
  version_requirements: !ruby/object:Gem::Requirement
135
152
  requirements:
136
153
  - - ">="
137
154
  - !ruby/object:Gem::Version
138
- version: '0'
155
+ version: '4.0'
156
+ - - "<"
157
+ - !ruby/object:Gem::Version
158
+ version: '6.1'
139
159
  - !ruby/object:Gem::Dependency
140
160
  name: snappy
141
161
  requirement: !ruby/object:Gem::Requirement
@@ -212,7 +232,7 @@ dependencies:
212
232
  requirements:
213
233
  - - ">="
214
234
  - !ruby/object:Gem::Version
215
- version: 3.0.0
235
+ version: 4.0.0
216
236
  - - "<"
217
237
  - !ruby/object:Gem::Version
218
238
  version: 5.0.0
@@ -222,7 +242,7 @@ dependencies:
222
242
  requirements:
223
243
  - - ">="
224
244
  - !ruby/object:Gem::Version
225
- version: 3.0.0
245
+ version: 4.0.0
226
246
  - - "<"
227
247
  - !ruby/object:Gem::Version
228
248
  version: 5.0.0
@@ -244,16 +264,16 @@ dependencies:
244
264
  name: prometheus-client
245
265
  requirement: !ruby/object:Gem::Requirement
246
266
  requirements:
247
- - - ">="
267
+ - - "~>"
248
268
  - !ruby/object:Gem::Version
249
- version: '0'
269
+ version: 0.10.0
250
270
  type: :development
251
271
  prerelease: false
252
272
  version_requirements: !ruby/object:Gem::Requirement
253
273
  requirements:
254
- - - ">="
274
+ - - "~>"
255
275
  - !ruby/object:Gem::Version
256
- version: '0'
276
+ version: 0.10.0
257
277
  - !ruby/object:Gem::Dependency
258
278
  name: ruby-prof
259
279
  requirement: !ruby/object:Gem::Requirement
@@ -332,6 +352,7 @@ extensions: []
332
352
  extra_rdoc_files: []
333
353
  files:
334
354
  - ".circleci/config.yml"
355
+ - ".github/workflows/stale.yml"
335
356
  - ".gitignore"
336
357
  - ".readygo"
337
358
  - ".rspec"
@@ -369,7 +390,10 @@ files:
369
390
  - lib/kafka/connection_builder.rb
370
391
  - lib/kafka/consumer.rb
371
392
  - lib/kafka/consumer_group.rb
393
+ - lib/kafka/consumer_group/assignor.rb
394
+ - lib/kafka/crc32_hash.rb
372
395
  - lib/kafka/datadog.rb
396
+ - lib/kafka/digest.rb
373
397
  - lib/kafka/fetch_operation.rb
374
398
  - lib/kafka/fetched_batch.rb
375
399
  - lib/kafka/fetched_batch_generator.rb
@@ -379,8 +403,10 @@ files:
379
403
  - lib/kafka/gzip_codec.rb
380
404
  - lib/kafka/heartbeat.rb
381
405
  - lib/kafka/instrumenter.rb
406
+ - lib/kafka/interceptors.rb
382
407
  - lib/kafka/lz4_codec.rb
383
408
  - lib/kafka/message_buffer.rb
409
+ - lib/kafka/murmur2_hash.rb
384
410
  - lib/kafka/offset_manager.rb
385
411
  - lib/kafka/partitioner.rb
386
412
  - lib/kafka/pause.rb
@@ -450,6 +476,7 @@ files:
450
476
  - lib/kafka/protocol/txn_offset_commit_request.rb
451
477
  - lib/kafka/protocol/txn_offset_commit_response.rb
452
478
  - lib/kafka/round_robin_assignment_strategy.rb
479
+ - lib/kafka/sasl/awsmskiam.rb
453
480
  - lib/kafka/sasl/gssapi.rb
454
481
  - lib/kafka/sasl/oauth.rb
455
482
  - lib/kafka/sasl/plain.rb
@@ -469,7 +496,7 @@ files:
469
496
  - ruby-kafka.gemspec
470
497
  homepage: https://github.com/zendesk/ruby-kafka
471
498
  licenses:
472
- - Apache License Version 2.0
499
+ - Apache-2.0
473
500
  metadata: {}
474
501
  post_install_message:
475
502
  rdoc_options: []
@@ -486,8 +513,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
486
513
  - !ruby/object:Gem::Version
487
514
  version: '0'
488
515
  requirements: []
489
- rubyforge_project:
490
- rubygems_version: 2.7.6
516
+ rubygems_version: 3.1.2
491
517
  signing_key:
492
518
  specification_version: 4
493
519
  summary: A client library for the Kafka distributed commit log.