karafka 2.4.12 → 2.4.13

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