karafka 2.4.12 → 2.4.13

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: f96e616f91d60d5054e276f52cc2ecde9d4a6f6c8e25b3d61c384936b9239e4f
4
- data.tar.gz: 8dfad6cc5d0cb4cdcc885b58e129929f67b0fdc4ee7922c21f9728cbdd863f5b
3
+ metadata.gz: 4556d1cccdb4ff51132f141f71a3c81d05dfdf71839241566bed12aa40ffcb83
4
+ data.tar.gz: 92af5f2db3c7614efdff4f4e0e92e012befd2b3f61bd89eb6b509cfb82a0569c
5
5
  SHA512:
6
- metadata.gz: bfea8217fb7ba89158b926417a0dd0cab42460a607b3bf25a62a400ab83e510806b966f81bd2ca76144e5aff62c99fb58b35be94ca1ad5c61ca113e760098243
7
- data.tar.gz: ee66d2c6a11dc6baac3cc836ea5455d6c88838f1db7c0f9af9a92c3f5d3c7b8dd9d1f8c42dac4c9908042da3b192d8579466deecc687126640fc7bc5e0aeafb2
6
+ metadata.gz: a1a28ea5cfc19cb7b3c2861fdc755bdb5e1c0e718b8b0244d87675e0d1cbae4cc568f8af28984a2845287e291207d4532e6c92cf87327ef17fb87f8263cd0d57
7
+ data.tar.gz: cc77cceea79fd8cbbe5013221fb4427daf9209aed781fff0d684ea1128a9b3984d69cb42dd14bd88c0d01968820e4f8e8ce3e1450d505614f7d12434cc46aedf
checksums.yaml.gz.sig CHANGED
Binary file
data/CHANGELOG.md CHANGED
@@ -1,5 +1,14 @@
1
1
  # Karafka Framework Changelog
2
2
 
3
+ ## 2.4.13 (2024-10-11)
4
+ - [Enhancement] Make declarative topics return different exit codes on migrable/non-migrable states (0 - no changes, 2 - changes).
5
+ - [Enhancement] Introduce `config.strict_declarative_topics` that should force declaratives on all non-pattern based topics and DLQ topics
6
+ - [Enhancement] Report ignored repartitioning to lower number of partitions in declarative topics.
7
+ - [Enhancement] Promote the `LivenessListener#healty?` to a public API.
8
+ - [Fix] Fix `Karafka::Errors::MissingBootFileError` when debugging in VScode with ruby-lsp.
9
+ - [Fix] Require `karafka-core` `>=` `2.4.4` to prevent dependencies conflicts.
10
+ - [Fix] Validate swarm cli and always parse options from argv (roelbondoc)
11
+
3
12
  ## 2.4.12 (2024-09-17)
4
13
  - **[Feature]** Provide Adaptive Iterator feature as a fast alternative to Long-Running Jobs (Pro).
5
14
  - [Enhancement] Provide `Consumer#each` as a delegation to messages batch.
data/Gemfile.lock CHANGED
@@ -1,9 +1,9 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- karafka (2.4.12)
4
+ karafka (2.4.13)
5
5
  base64 (~> 0.2)
6
- karafka-core (>= 2.4.3, < 2.5.0)
6
+ karafka-core (>= 2.4.4, < 2.5.0)
7
7
  karafka-rdkafka (>= 0.17.2)
8
8
  waterdrop (>= 2.7.3, < 3.0.0)
9
9
  zeitwerk (~> 2.3)
data/bin/integrations CHANGED
@@ -45,7 +45,10 @@ class Scenario
45
45
  'shutdown/on_hanging_on_shutdown_job_and_a_shutdown_spec.rb' => [2].freeze,
46
46
  'shutdown/on_hanging_listener_and_shutdown_spec.rb' => [2].freeze,
47
47
  'swarm/forceful_shutdown_of_hanging_spec.rb' => [2].freeze,
48
- 'instrumentation/post_errors_instrumentation_error_spec.rb' => [1].freeze
48
+ 'instrumentation/post_errors_instrumentation_error_spec.rb' => [1].freeze,
49
+ 'cli/declaratives/delete/existing_with_exit_code_spec.rb' => [2].freeze,
50
+ 'cli/declaratives/create/new_with_exit_code_spec.rb' => [2].freeze,
51
+ 'cli/declaratives/plan/when_changes_with_detailed_exit_code_spec.rb' => [2].freeze
49
52
  }.freeze
50
53
 
51
54
  private_constant :MAX_RUN_TIME, :EXIT_CODES
@@ -14,6 +14,7 @@ en:
14
14
  pause_max_timeout_format: needs to be an integer bigger than 0
15
15
  pause_with_exponential_backoff_format: needs to be either true or false
16
16
  strict_topics_namespacing_format: needs to be either true or false
17
+ strict_declarative_topics_format: needs to be either true or false
17
18
  shutdown_timeout_format: needs to be an integer bigger than 0
18
19
  max_wait_time_format: needs to be an integer bigger than 0
19
20
  max_wait_time_max_wait_time_vs_swarm_node_report_timeout: >
@@ -147,6 +148,9 @@ en:
147
148
  all topic names within a single consumer group must be unique considering namespacing styles
148
149
  disable this validation by setting config.strict_topics_namespacing to false
149
150
 
151
+ routing:
152
+ without_declarative_definition: lacks explicit declarative topics definition
153
+
150
154
  job_options:
151
155
  missing: needs to be present
152
156
  dispatch_method_format: needs to be either :produce_async or :produce_sync
data/karafka.gemspec CHANGED
@@ -22,7 +22,7 @@ Gem::Specification.new do |spec|
22
22
  DESC
23
23
 
24
24
  spec.add_dependency 'base64', '~> 0.2'
25
- spec.add_dependency 'karafka-core', '>= 2.4.3', '< 2.5.0'
25
+ spec.add_dependency 'karafka-core', '>= 2.4.4', '< 2.5.0'
26
26
  spec.add_dependency 'karafka-rdkafka', '>= 0.17.2'
27
27
  spec.add_dependency 'waterdrop', '>= 2.7.3', '< 3.0.0'
28
28
  spec.add_dependency 'zeitwerk', '~> 2.3'
@@ -96,7 +96,7 @@ module Karafka
96
96
  *[names, option[2], option[1]].flatten
97
97
  ) { |value| options[option[0]] = value }
98
98
  end
99
- end.parse!
99
+ end.parse(ARGV)
100
100
 
101
101
  options
102
102
  end
@@ -16,7 +16,10 @@ module Karafka
16
16
  return false
17
17
  end
18
18
 
19
+ changes = false
20
+
19
21
  unless topics_to_create.empty?
22
+ changes = true
20
23
  puts 'Following topics will be created:'
21
24
  puts
22
25
 
@@ -33,20 +36,54 @@ module Karafka
33
36
  end
34
37
 
35
38
  unless topics_to_repartition.empty?
36
- puts 'Following topics will be repartitioned:'
37
- puts
39
+ upscale = {}
40
+ downscale = {}
38
41
 
39
42
  topics_to_repartition.each do |topic, partitions|
40
43
  from = partitions
41
44
  to = topic.declaratives.partitions
42
45
 
43
- puts " #{yellow('~')} #{topic.name}:"
44
- puts " #{yellow('~')} partitions: \"#{red(from)}\" #{grey('=>')} \"#{green(to)}\""
46
+ if from < to
47
+ upscale[topic] = partitions
48
+ else
49
+ downscale[topic] = partitions
50
+ end
51
+ end
52
+
53
+ unless upscale.empty?
54
+ changes = true
55
+ puts 'Following topics will be repartitioned:'
56
+ puts
57
+
58
+ upscale.each do |topic, partitions|
59
+ from = partitions
60
+ to = topic.declaratives.partitions
61
+ y = yellow('~')
62
+ puts " #{y} #{topic.name}:"
63
+ puts " #{y} partitions: \"#{red(from)}\" #{grey('=>')} \"#{green(to)}\""
64
+ puts
65
+ end
66
+ end
67
+
68
+ unless downscale.empty?
69
+ puts(
70
+ 'Following topics repartitioning will be ignored as downscaling is not supported:'
71
+ )
45
72
  puts
73
+
74
+ downscale.each do |topic, partitions|
75
+ from = partitions
76
+ to = topic.declaratives.partitions
77
+
78
+ puts " #{grey('~')} #{topic.name}:"
79
+ puts " #{grey('~')} partitions: \"#{grey(from)}\" #{grey('=>')} \"#{grey(to)}\""
80
+ puts
81
+ end
46
82
  end
47
83
  end
48
84
 
49
85
  unless topics_to_alter.empty?
86
+ changes = true
50
87
  puts 'Following topics will have configuration changes:'
51
88
  puts
52
89
 
@@ -65,7 +102,7 @@ module Karafka
65
102
  end
66
103
  end
67
104
 
68
- true
105
+ changes
69
106
  end
70
107
 
71
108
  private
@@ -10,26 +10,53 @@ module Karafka
10
10
  )
11
11
 
12
12
  desc 'Allows for the topics management'
13
+
14
+ option(
15
+ :detailed_exitcode,
16
+ 'Exists with 0 when no changes, 1 when error and 2 when changes present or applied',
17
+ TrueClass,
18
+ %w[
19
+ --detailed_exitcode
20
+ ]
21
+ )
22
+
23
+ # We exit with 0 if no changes happened
24
+ NO_CHANGES_EXIT_CODE = 0
25
+
26
+ # When any changes happened (or could happen) we return 2 because 1 is default when Ruby
27
+ # crashes
28
+ CHANGES_EXIT_CODE = 2
29
+
30
+ private_constant :NO_CHANGES_EXIT_CODE, :CHANGES_EXIT_CODE
31
+
13
32
  # @param action [String] action we want to take
14
33
  def call(action = 'missing')
15
- case action
16
- when 'create'
17
- Topics::Create.new.call
18
- when 'delete'
19
- Topics::Delete.new.call
20
- when 'reset'
21
- Topics::Reset.new.call
22
- when 'repartition'
23
- Topics::Repartition.new.call
24
- when 'migrate'
25
- Topics::Migrate.new.call
26
- when 'align'
27
- Topics::Align.new.call
28
- when 'plan'
29
- Topics::Plan.new.call
30
- else
31
- raise ::ArgumentError, "Invalid topics action: #{action}"
32
- end
34
+ detailed_exit_code = options.fetch(:detailed_exitcode, false)
35
+
36
+ command = case action
37
+ when 'create'
38
+ Topics::Create
39
+ when 'delete'
40
+ Topics::Delete
41
+ when 'reset'
42
+ Topics::Reset
43
+ when 'repartition'
44
+ Topics::Repartition
45
+ when 'migrate'
46
+ Topics::Migrate
47
+ when 'align'
48
+ Topics::Align
49
+ when 'plan'
50
+ Topics::Plan
51
+ else
52
+ raise ::ArgumentError, "Invalid topics action: #{action}"
53
+ end
54
+
55
+ changes = command.new.call
56
+
57
+ return unless detailed_exit_code
58
+
59
+ changes ? exit(CHANGES_EXIT_CODE) : exit(NO_CHANGES_EXIT_CODE)
33
60
  end
34
61
  end
35
62
  end
@@ -34,6 +34,7 @@ module Karafka
34
34
  required(:max_wait_time) { |val| val.is_a?(Integer) && val.positive? }
35
35
  required(:group_id) { |val| val.is_a?(String) && Contracts::TOPIC_REGEXP.match?(val) }
36
36
  required(:kafka) { |val| val.is_a?(Hash) && !val.empty? }
37
+ required(:strict_declarative_topics) { |val| [true, false].include?(val) }
37
38
 
38
39
  nested(:swarm) do
39
40
  required(:nodes) { |val| val.is_a?(Integer) && val.positive? }
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Karafka
4
+ module Contracts
5
+ # Ensures that routing wide rules are obeyed
6
+ class Routing < Base
7
+ configure do |config|
8
+ config.error_messages = YAML.safe_load(
9
+ File.read(
10
+ File.join(Karafka.gem_root, 'config', 'locales', 'errors.yml')
11
+ )
12
+ ).fetch('en').fetch('validations').fetch('routing')
13
+ end
14
+
15
+ # Ensures, that when declarative topics strict requirement is on, all topics have
16
+ # declarative definition (including DLQ topics)
17
+ # @note It will ignore routing pattern topics because those topics are virtual
18
+ virtual do |data, errors|
19
+ next unless errors.empty?
20
+ # Do not validate declaratives unless required and explicitly enabled
21
+ next unless Karafka::App.config.strict_declarative_topics
22
+
23
+ # Collects declarative topics. Please note, that any topic that has a `#topic` reference,
24
+ # will be declarative by default unless explicitly disabled. This however does not apply
25
+ # to the DLQ definitions
26
+ dec_topics = Set.new
27
+ # All topics including the DLQ topics names that are marked as active
28
+ topics = Set.new
29
+
30
+ data.each do |consumer_group|
31
+ consumer_group[:topics].each do |topic|
32
+ pat = topic[:patterns]
33
+ # Ignore pattern topics because they won't exist and should not be declarative
34
+ # managed
35
+ topics << topic[:name] if !pat || !pat[:active]
36
+
37
+ dlq = topic[:dead_letter_queue]
38
+ topics << dlq[:topic] if dlq[:active]
39
+
40
+ dec = topic[:declaratives]
41
+
42
+ dec_topics << topic[:name] if dec[:active]
43
+ end
44
+ end
45
+
46
+ missing_dec = topics - dec_topics
47
+
48
+ next if missing_dec.empty?
49
+
50
+ missing_dec.map do |topic_name|
51
+ [
52
+ [:topics, topic_name],
53
+ :without_declarative_definition
54
+ ]
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
@@ -29,13 +29,13 @@ module Karafka
29
29
  @port = port
30
30
  end
31
31
 
32
- private
33
-
34
32
  # @return [Boolean] true if all good, false if we should tell k8s to kill this process
35
33
  def healthy?
36
34
  raise NotImplementedError, 'Implement in a subclass'
37
35
  end
38
36
 
37
+ private
38
+
39
39
  # Responds to a HTTP request with the process liveness status
40
40
  def respond
41
41
  client = @server.accept
@@ -112,6 +112,17 @@ module Karafka
112
112
  clear_polling_tick
113
113
  end
114
114
 
115
+ # Did we exceed any of the ttls
116
+ # @return [String] 204 string if ok, 500 otherwise
117
+ def healthy?
118
+ time = monotonic_now
119
+
120
+ return false if @pollings.values.any? { |tick| (time - tick) > @polling_ttl }
121
+ return false if @consumptions.values.any? { |tick| (time - tick) > @consuming_ttl }
122
+
123
+ true
124
+ end
125
+
115
126
  private
116
127
 
117
128
  # Wraps the logic with a mutex
@@ -152,17 +163,6 @@ module Karafka
152
163
  @consumptions.delete(thread_id)
153
164
  end
154
165
  end
155
-
156
- # Did we exceed any of the ttls
157
- # @return [String] 204 string if ok, 500 otherwise
158
- def healthy?
159
- time = monotonic_now
160
-
161
- return false if @pollings.values.any? { |tick| (time - tick) > @polling_ttl }
162
- return false if @consumptions.values.any? { |tick| (time - tick) > @consuming_ttl }
163
-
164
- true
165
- end
166
166
  end
167
167
  end
168
168
  end
@@ -48,6 +48,10 @@ module Karafka
48
48
 
49
49
  instance_eval(&block)
50
50
 
51
+ # Ensures high-level routing details consistency
52
+ # Contains checks that require knowledge about all the consumer groups to operate
53
+ Contracts::Routing.new.validate!(map(&:to_h))
54
+
51
55
  each do |consumer_group|
52
56
  # Validate consumer group settings
53
57
  Contracts::ConsumerGroup.new.validate!(consumer_group.to_h)
@@ -68,6 +68,11 @@ module Karafka
68
68
  setting :strict_topics_namespacing, default: true
69
69
  # option [String] default consumer group name for implicit routing
70
70
  setting :group_id, default: 'app'
71
+ # option [Boolean] when set to true, it will validate as part of the routing validation, that
72
+ # all topics and DLQ topics (even not active) have the declarative topics definitions.
73
+ # Really useful when you want to ensure that all topics in routing are managed via
74
+ # declaratives.
75
+ setting :strict_declarative_topics, default: false
71
76
 
72
77
  setting :oauth do
73
78
  # option [false, #call] Listener for using oauth bearer. This listener will be able to
@@ -20,7 +20,9 @@ module Karafka
20
20
  shutdown_timeout: %i[shutdown_timeout],
21
21
  supervision_sleep: %i[internal supervision_sleep],
22
22
  forceful_exit_code: %i[internal forceful_exit_code],
23
- process: %i[internal process]
23
+ process: %i[internal process],
24
+ cli_contract: %i[internal cli contract],
25
+ activity_manager: %i[internal routing activity_manager]
24
26
  )
25
27
 
26
28
  # How long extra should we wait on shutdown before forceful termination
@@ -39,6 +41,9 @@ module Karafka
39
41
 
40
42
  # Creates needed number of forks, installs signals and starts supervision
41
43
  def run
44
+ # Validate the CLI provided options the same way as we do for the regular server
45
+ cli_contract.validate!(activity_manager.to_h)
46
+
42
47
  # Close producer just in case. While it should not be used, we do not want even a
43
48
  # theoretical case since librdkafka is not thread-safe.
44
49
  # We close it prior to forking just to make sure, there is no issue with initialized
@@ -67,7 +72,11 @@ module Karafka
67
72
  lock
68
73
  control
69
74
  end
70
- # If anything went wrong, signal this and die
75
+
76
+ # If the cli contract validation failed reraise immediately and stop the process
77
+ rescue Karafka::Errors::InvalidConfigurationError => e
78
+ raise e
79
+ # If anything went wrong during supervision, signal this and die
71
80
  # Supervisor is meant to be thin and not cause any issues. If you encounter this case
72
81
  # please report it as it should be considered critical
73
82
  rescue StandardError => e
@@ -3,5 +3,5 @@
3
3
  # Main module namespace
4
4
  module Karafka
5
5
  # Current Karafka version
6
- VERSION = '2.4.12'
6
+ VERSION = '2.4.13'
7
7
  end
data/lib/karafka.rb CHANGED
@@ -49,17 +49,29 @@ module Karafka
49
49
  @monitor ||= App.config.monitor
50
50
  end
51
51
 
52
- # @return [String] root path of this gem
52
+ # @return [Pathname] root path of this gem
53
53
  def gem_root
54
54
  Pathname.new(File.expand_path('..', __dir__))
55
55
  end
56
56
 
57
- # @return [String] Karafka app root path (user application path)
57
+ # @return [Pathname] Karafka app root path (user application path)
58
58
  def root
59
- Pathname.new(ENV['KARAFKA_ROOT_DIR'] || File.dirname(ENV['BUNDLE_GEMFILE']))
59
+ # If user points to a different root explicitly, use it
60
+ return Pathname.new(ENV['KARAFKA_ROOT_DIR']) if ENV['KARAFKA_ROOT_DIR']
61
+
62
+ # By default we infer the project root from bundler.
63
+ # We cannot use the BUNDLE_GEMFILE env directly because it may be altered by things like
64
+ # ruby-lsp. Instead we always fallback to the most outer Gemfile. In most of the cases, it
65
+ # won't matter but in case of some automatic setup alterations like ruby-lsp, the location
66
+ # from which the project starts may not match the original Gemfile.
67
+ Pathname.new(
68
+ File.dirname(
69
+ Bundler.with_unbundled_env { Bundler.default_gemfile }
70
+ )
71
+ )
60
72
  end
61
73
 
62
- # @return [String] path to Karafka gem root core
74
+ # @return [Pathname] path to Karafka gem root core
63
75
  def core_root
64
76
  Pathname.new(File.expand_path('karafka', __dir__))
65
77
  end
data.tar.gz.sig CHANGED
Binary file
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: karafka
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.4.12
4
+ version: 2.4.13
5
5
  platform: ruby
6
6
  authors:
7
7
  - Maciej Mensfeld
@@ -35,7 +35,7 @@ cert_chain:
35
35
  i9zWxov0mr44TWegTVeypcWGd/0nxu1+QHVNHJrpqlPBRvwQsUm7fwmRInGpcaB8
36
36
  ap8wNYvryYzrzvzUxIVFBVM5PacgkFqRmolCa8I7tdKQN+R1
37
37
  -----END CERTIFICATE-----
38
- date: 2024-09-17 00:00:00.000000000 Z
38
+ date: 2024-10-11 00:00:00.000000000 Z
39
39
  dependencies:
40
40
  - !ruby/object:Gem::Dependency
41
41
  name: base64
@@ -57,7 +57,7 @@ dependencies:
57
57
  requirements:
58
58
  - - ">="
59
59
  - !ruby/object:Gem::Version
60
- version: 2.4.3
60
+ version: 2.4.4
61
61
  - - "<"
62
62
  - !ruby/object:Gem::Version
63
63
  version: 2.5.0
@@ -67,7 +67,7 @@ dependencies:
67
67
  requirements:
68
68
  - - ">="
69
69
  - !ruby/object:Gem::Version
70
- version: 2.4.3
70
+ version: 2.4.4
71
71
  - - "<"
72
72
  - !ruby/object:Gem::Version
73
73
  version: 2.5.0
@@ -219,6 +219,7 @@ files:
219
219
  - lib/karafka/contracts/base.rb
220
220
  - lib/karafka/contracts/config.rb
221
221
  - lib/karafka/contracts/consumer_group.rb
222
+ - lib/karafka/contracts/routing.rb
222
223
  - lib/karafka/contracts/server_cli_options.rb
223
224
  - lib/karafka/contracts/topic.rb
224
225
  - lib/karafka/deserializers/headers.rb
metadata.gz.sig CHANGED
Binary file