sidekiq-cron 2.1.0 → 2.4.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
  SHA256:
3
- metadata.gz: 1d5f570d2d2d72cdb4fc1a2458dc6cae7abfa5a0da1f27915310b5d5dc835e89
4
- data.tar.gz: 514d74f88c65af3db1956190ca9092d4f1a95d6bce4b0a9191463e2bb0f9181a
3
+ metadata.gz: 24a9263df38117d217eaff38d09f5cc69029d2998a3cc07fd5f21878b340305c
4
+ data.tar.gz: 9cf118cc907fd84abdc0c0123fb23d6745b9fee021557560e35c55f9ff99fd35
5
5
  SHA512:
6
- metadata.gz: 61f4af041526789dcf51264baa88a5e1855fa124a0fe9f2c5ed04eec36bd4b617aae1152890ab281d5938914725ba9cce812f70bc258a6e4b8d82b642882212a
7
- data.tar.gz: 9e82488bd6459b3ce373e52c94fc3607631b5fc67fa58e578d190368a09f4edc3b4502318646d5285e9fb9d139a080d90d560b28ad31a56e9d01d81d1dc0a2f8
6
+ metadata.gz: 238cd9ccbf1f621090eca667e9e1b4c198589897869c7e8d89746886297bac0456dcd0452aa91f0e5e07af5e4e686d93f140089a44cd676cdf1711c45ced4e40
7
+ data.tar.gz: 404d91026a551fefd3e2a4c37bae8220e522b9a38a377cbbdc2f692331926495835feb2a4decf8dfe4a059e84ff6c45fc99e6c96c0f6156d53130586bf70b1bb
data/CHANGELOG.md CHANGED
@@ -2,6 +2,33 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file.
4
4
 
5
+ ## 2.4.0
6
+
7
+ - Fix reflected XSS on Sidekiq-UI (https://github.com/sidekiq-cron/sidekiq-cron/pull/568)
8
+ - Add `cron_process_count_override` config option (https://github.com/sidekiq-cron/sidekiq-cron/pull/572)
9
+ - Fix Spanish translation for enabled/disabled states (https://github.com/sidekiq-cron/sidekiq-cron/pull/575)
10
+ - Allow to conditionally disable Sidekiq-Cron (https://github.com/sidekiq-cron/sidekiq-cron/pull/574)
11
+
12
+ ## 2.3.1
13
+
14
+ - Fix manually launch enqueue job not working from web UI (https://github.com/sidekiq-cron/sidekiq-cron/pull/564)
15
+
16
+ ## 2.3.0
17
+
18
+ - **WARNING** The default value for `available_namespaces` has changed from `nil` to `[default_namespace]`. Please refer to the [migration guide](https://github.com/sidekiq-cron/sidekiq-cron/blob/master/README.md#migrating-to-23) for further details (https://github.com/sidekiq-cron/sidekiq-cron/pull/552)
19
+ - Fix deprecation warning for using raw params in WEB extension (https://github.com/sidekiq-cron/sidekiq-cron/pull/547)
20
+ - Allow for sidekiq embedded configuration (https://github.com/sidekiq-cron/sidekiq-cron/pull/549)
21
+ - Load schedule file within sidekiq callback (https://github.com/sidekiq-cron/sidekiq-cron/pull/550)
22
+ - Fix missing default namespace if namespaces are explicitly provided (https://github.com/sidekiq-cron/sidekiq-cron/pull/551)
23
+
24
+ ## 2.2.0
25
+
26
+ - Add support for Sidekiq v8 (https://github.com/sidekiq-cron/sidekiq-cron/pull/531, https://github.com/sidekiq-cron/sidekiq-cron/pull/538)
27
+ - Always show parsed cron expression in web UI (https://github.com/sidekiq-cron/sidekiq-cron/pull/535)
28
+ - Add fallback to .yaml extension (https://github.com/sidekiq-cron/sidekiq-cron/pull/534)
29
+ - Refactor constantize helper to use Object.const_get (https://github.com/sidekiq-cron/sidekiq-cron/pull/541)
30
+ - Allow testing of schedule by library consumers (https://github.com/sidekiq-cron/sidekiq-cron/pull/543)
31
+
5
32
  ## 2.1.0
6
33
 
7
34
  - Add `available_namespaces` configuration option (https://github.com/sidekiq-cron/sidekiq-cron/pull/524)
data/Gemfile CHANGED
@@ -5,5 +5,5 @@ gemspec
5
5
  # To test different Sidekiq versions
6
6
  gem "sidekiq", ENV.fetch("SIDEKIQ_VERSION", ">= 6")
7
7
 
8
- # To test different Active Job versions
9
- gem "activejob", ENV.fetch("ACTIVE_JOB_VERSION", "~> 7")
8
+ # To test different Rails versions
9
+ gem "rails", ENV.fetch("RAILS_VERSION", "~> 7")
data/README.md CHANGED
@@ -56,7 +56,7 @@ gem "sidekiq-cron"
56
56
  'active_job' => true, # enqueue job through Active Job interface
57
57
  'queue_name_prefix' => 'prefix', # Active Job queue with prefix
58
58
  'queue_name_delimiter' => '.', # Active Job queue with custom delimiter (default: '_')
59
- 'description' => 'A sentence describing what work this job performs'
59
+ 'description' => 'A sentence describing what work this job performs',
60
60
  'status' => 'disabled' # default: enabled
61
61
  }
62
62
  ```
@@ -69,13 +69,14 @@ All configuration options:
69
69
 
70
70
  ```ruby
71
71
  Sidekiq::Cron.configure do |config|
72
+ config.enabled = false # Default is true
72
73
  config.cron_poll_interval = 10 # Default is 30
73
74
  config.cron_schedule_file = 'config/my_schedule.yml' # Default is 'config/schedule.yml'
74
75
  config.cron_history_size = 20 # Default is 10
75
76
  config.default_namespace = 'statistics' # Default is 'default'
77
+ config.available_namespaces = %w[statistics maintenance billing] # Default is `[config.default_namespace]`
76
78
  config.natural_cron_parsing_mode = :strict # Default is :single
77
79
  config.reschedule_grace_period = 300 # Default is 60
78
- config.available_namespaces = %w[maintenance billing] # Default is `nil`
79
80
  end
80
81
  ```
81
82
 
@@ -163,7 +164,7 @@ In the case you'd like to change this value, you can change it via the following
163
164
 
164
165
  ```ruby
165
166
  Sidekiq::Cron.configure do |config|
166
- config.default_namespace = 'statics'
167
+ config.default_namespace = 'statistics'
167
168
  end
168
169
  ```
169
170
 
@@ -179,11 +180,21 @@ Sidekiq::Cron::Job.all('YOUR_OLD_NAMESPACE_NAME').each { |job| job.destroy }
179
180
 
180
181
  #### Available namespaces
181
182
 
182
- By default, Sidekiq Cron retrieves all existing jobs from Redis to determine the namespaces your application uses. However, this approach may not be suitable for large Redis installations. To address this, you can explicitly specify the list of available namespaces using the `available_namespaces` configuration option.
183
+ By default, Sidekiq Cron uses the available_namespaces configuration option to determine which namespaces your application utilizes. The default namespace (`"default"`, by default) is always included in the list of available namespaces.
183
184
 
184
- If `available_namespaces` is set and a job is created with an unexpected namespace, a warning will be printed, and the job will be assigned to the default namespace.
185
+ If you want Sidekiq Cron to automatically detect existing namespaces from the Redis database, you can set `available_namespaces` to the special option `:auto`.
185
186
 
186
- For more details and discussion, see [this issue](https://github.com/sidekiq-cron/sidekiq-cron/issues/516).
187
+ If available_namespaces is explicitly set and a job is created with an unexpected namespace, a warning will be printed, and the job will be assigned to the default namespace.
188
+
189
+ #### Migrating to 2.3
190
+
191
+ As discussed in [this issue](https://github.com/sidekiq-cron/sidekiq-cron/issues/516), the approach introduced in Sidekiq Cron 2.0 for determining available namespaces using the `KEYS` command is not acceptable. Therefore, starting from version 2.3, namespacing has been reworked:
192
+
193
+ - If you were not using the namespacing feature, no action is required. You can even remove `available_namespaces = %w[default]`, as it is now the default.
194
+
195
+ - If you were using the namespacing feature and explicitly specified available namespaces as a list, no changes are needed.
196
+
197
+ - If you were using the namespacing feature and relied on automatic namespace inference, you should either specify all used namespaces explicitly or set `available_namespaces` to `:auto` to maintain automatic detection. However, note that this approach does not scale well (see the referenced issue for details).
187
198
 
188
199
  #### Usage
189
200
 
@@ -511,6 +522,61 @@ Sidekiq::Cron.configure do |config|
511
522
  end
512
523
  ```
513
524
 
525
+ You can also disable the entire engine by setting `enabled` to `false`. When disabled, Sidekiq-Cron will skip loading the schedule file on startup, which is useful for environments or specific Sidekiq processes where you don't need cron job scheduling at all:
526
+
527
+ ```ruby
528
+ Sidekiq::Cron.configure do |config|
529
+ config.enabled = false
530
+ end
531
+ ```
532
+
533
+ Sidekiq will internally determine the process count by checking Redis but if polling has been disabled from some Sidekiq processes by setting the cron poll interval to zero, as explained above, that count might be incorrect. In that case, it is possible to override the default process count for Sidekiq Cron. This affects the way the random poll interval is calculated internally.
534
+
535
+ ```ruby
536
+ Sidekiq::Cron.configure do |config|
537
+ config.cron_poll_process_count = 2
538
+ end
539
+ ```
540
+
541
+ ## Testing your configuration
542
+
543
+ You can test your application's configuration by loading the schedule in your test suite. Below is an example using RSpec in a Rails project:
544
+
545
+ ```ruby
546
+ # spec/cron_schedule_spec.rb
547
+ require "rails_helper"
548
+
549
+ RSpec.describe "Cron Schedule" do
550
+ let(:schedule_loader) { Sidekiq::Cron::ScheduleLoader.new }
551
+ let(:all_jobs) { Sidekiq::Cron::Job.all }
552
+
553
+ # Confirms that `config.cron_schedule_file` points to a real file.
554
+ it "has a schedule file" do
555
+ expect(schedule_loader).to have_schedule_file
556
+ end
557
+
558
+ # Confirms that no jobs in the schedule have an invalid cron string.
559
+ it "does not return any errors" do
560
+ expect(schedule_loader.load_schedule).to be_empty
561
+ end
562
+
563
+ # May be subject to churn, but adds confidence.
564
+ it "adds the expected number of jobs" do
565
+ schedule_loader.load_schedule
566
+ expect(all_jobs.size).to eq 5
567
+ end
568
+
569
+ # Confirms that all job classes exist.
570
+ it "has a valid class for each added job" do
571
+ schedule_loader.load_schedule
572
+ # Shows that all classes exist (as we can constantize the names without raising).
573
+ job_classes = all_jobs.map { |job| job.klass.constantize }
574
+ # Naive check that classes are sidekiq jobs (as they all have `.perfrom_async`).
575
+ expect(job_classes).to all(respond_to(:perform_async))
576
+ end
577
+ end
578
+ ```
579
+
514
580
  ## Contributing
515
581
 
516
582
  **Thanks to all [contributors](https://github.com/sidekiq-cron/sidekiq-cron/graphs/contributors), you’re awesome and this wouldn’t be possible without you!**
@@ -17,8 +17,8 @@ module Sidekiq
17
17
  # Use serialize/deserialize key of GlobalID.
18
18
  GLOBALID_KEY = "_sc_globalid"
19
19
 
20
- attr_accessor :name, :namespace, :cron, :description, :klass, :args, :message
21
- attr_reader :last_enqueue_time, :fetch_missing_args, :source
20
+ attr_accessor :name, :namespace, :cron, :description, :klass, :message
21
+ attr_reader :last_enqueue_time, :fetch_missing_args, :source, :args
22
22
 
23
23
  def initialize input_args = {}
24
24
  args = Hash[input_args.map{ |k, v| [k.to_s, v] }]
@@ -32,7 +32,7 @@ module Sidekiq
32
32
 
33
33
  default_namespace = Sidekiq::Cron.configuration.default_namespace
34
34
  @namespace = args["namespace"] || default_namespace
35
- if Sidekiq::Cron::Namespace.available_namespaces_provided? && !Sidekiq::Cron::Namespace.all.include?(@namespace) && @namespace != default_namespace
35
+ if Sidekiq::Cron::Namespace.available_namespaces_provided? && !Sidekiq::Cron::Namespace.all.include?(@namespace)
36
36
  Sidekiq.logger.warn { "Cron Jobs - unexpected namespace #{@namespace} encountered. Assigning to default namespace." }
37
37
  @namespace = default_namespace
38
38
  end
@@ -132,12 +132,7 @@ module Sidekiq
132
132
  def enqueue! time = Time.now.utc
133
133
  @last_enqueue_time = time
134
134
 
135
- klass_const =
136
- begin
137
- Sidekiq::Cron::Support.constantize(@klass.to_s)
138
- rescue NameError
139
- nil
140
- end
135
+ klass_const = Sidekiq::Cron::Support.safe_constantize(@klass.to_s)
141
136
 
142
137
  jid =
143
138
  if klass_const
@@ -160,9 +155,10 @@ module Sidekiq
160
155
  end
161
156
 
162
157
  def is_active_job?(klass = nil)
163
- @active_job || defined?(::ActiveJob::Base) && (klass || Sidekiq::Cron::Support.constantize(@klass.to_s)) < ::ActiveJob::Base
164
- rescue NameError
165
- false
158
+ @active_job || defined?(::ActiveJob::Base) && begin
159
+ klass ||= Sidekiq::Cron::Support.safe_constantize(@klass.to_s)
160
+ klass ? klass < ::ActiveJob::Base : false
161
+ end
166
162
  end
167
163
 
168
164
  def date_as_argument?
@@ -383,7 +379,7 @@ module Sidekiq
383
379
 
384
380
  def human_cron
385
381
  Cronex::ExpressionDescriptor.new(cron).description
386
- rescue => e
382
+ rescue
387
383
  cron
388
384
  end
389
385
 
@@ -600,6 +596,10 @@ module Sidekiq
600
596
  @args = parse_args(args)
601
597
  end
602
598
 
599
+ def cron_expression_string
600
+ parsed_cron.to_cron_s
601
+ end
602
+
603
603
  private
604
604
 
605
605
  def parsed_cron
@@ -693,7 +693,7 @@ module Sidekiq
693
693
  def self.job_keys_from_namespace(namespace = Sidekiq::Cron.configuration.default_namespace)
694
694
  Sidekiq.redis do |conn|
695
695
  if namespace == '*'
696
- namespaces = Sidekiq::Cron.configuration.available_namespaces&.map { jobs_key(_1) } || conn.keys(jobs_key(namespace))
696
+ namespaces = Sidekiq::Cron::Namespace.all.map { jobs_key(_1) }
697
697
  namespaces.flat_map { |name| conn.smembers(name) }
698
698
  else
699
699
  conn.smembers(jobs_key(namespace))
@@ -800,11 +800,7 @@ module Sidekiq
800
800
  end
801
801
 
802
802
  def get_job_options(klass, args)
803
- klass = klass.is_a?(Class) ? klass : begin
804
- Sidekiq::Cron::Support.constantize(klass)
805
- rescue NameError
806
- # noop
807
- end
803
+ klass = klass.is_a?(Class) ? klass : Sidekiq::Cron::Support.safe_constantize(klass)
808
804
 
809
805
  if klass.nil?
810
806
  # Unknown class
@@ -13,6 +13,7 @@ module Sidekiq
13
13
  # Add cron poller and execute normal initialize of Sidekiq launcher.
14
14
  def initialize(config, **kwargs)
15
15
  config[:cron_poll_interval] = Sidekiq::Cron.configuration.cron_poll_interval.to_i
16
+ config[:cron_poll_process_count] = Sidekiq::Cron.configuration.cron_poll_process_count
16
17
 
17
18
  @cron_poller = Sidekiq::Cron::Poller.new(config) if config[:cron_poll_interval] > 0
18
19
  super
@@ -18,5 +18,5 @@ es:
18
18
  LastEnqueued: Último trabajo en cola
19
19
  NoCronJobsWereFound: No se encontraron trabajos
20
20
  NoHistoryWereFound: No se encontró histórico de trabajos
21
- disabled: activo
22
- enabled: inactivo
21
+ disabled: inactivo
22
+ enabled: activo
@@ -2,25 +2,20 @@ module Sidekiq
2
2
  module Cron
3
3
  class Namespace
4
4
  def self.all
5
- namespaces = Sidekiq::Cron.configuration.available_namespaces
6
- return namespaces if namespaces
7
-
8
- Sidekiq.redis do |conn|
9
- namespaces = conn.keys('cron_jobs:*').collect do |key|
10
- key.split(':').last
5
+ namespaces = case (available_namespaces = Sidekiq::Cron.configuration.available_namespaces)
6
+ when NilClass then []
7
+ when Array then available_namespaces
8
+ when :auto
9
+ Sidekiq.redis do |conn|
10
+ conn.keys('cron_jobs:*').collect do |key|
11
+ key.split(':').last
12
+ end
13
+ end
14
+ else
15
+ raise ArgumentError, "Unexpected value provided for `available_namespaces`: #{available_namespaces.inspect}"
11
16
  end
12
- end
13
-
14
- # Adds the default namespace if not present
15
- has_default = namespaces.detect do |name|
16
- name == Sidekiq::Cron.configuration.default_namespace
17
- end
18
-
19
- unless has_default
20
- namespaces << Sidekiq::Cron.configuration.default_namespace
21
- end
22
17
 
23
- namespaces
18
+ namespaces | [Sidekiq::Cron.configuration.default_namespace]
24
19
  end
25
20
 
26
21
  def self.all_with_count
@@ -33,15 +28,15 @@ module Sidekiq
33
28
  end
34
29
 
35
30
  def self.count(name = Sidekiq::Cron.configuration.default_namespace)
36
- out = 0
37
31
  Sidekiq.redis do |conn|
38
- out = conn.scard("cron_jobs:#{name}")
32
+ conn.scard("cron_jobs:#{name}")
39
33
  end
40
- out
41
34
  end
42
35
 
43
36
  def self.available_namespaces_provided?
44
- !!Sidekiq::Cron.configuration.available_namespaces
37
+ available_namespaces = Sidekiq::Cron.configuration.available_namespaces
38
+
39
+ available_namespaces != nil && available_namespaces != :auto
45
40
  end
46
41
  end
47
42
  end
@@ -43,6 +43,10 @@ module Sidekiq
43
43
  def poll_interval_average(process_count = 1)
44
44
  @config[:cron_poll_interval]
45
45
  end
46
+
47
+ def process_count
48
+ @config[:cron_poll_process_count] || super
49
+ end
46
50
  end
47
51
  end
48
52
  end
@@ -1,16 +1,62 @@
1
- Sidekiq.configure_server do |config|
2
- schedule_file = Sidekiq::Cron.configuration.cron_schedule_file
3
-
4
- if File.exist?(schedule_file)
5
- config.on(:startup) do
6
- schedule = Sidekiq::Cron::Support.load_yaml(ERB.new(IO.read(schedule_file)).result)
7
- if schedule.kind_of?(Hash)
8
- Sidekiq::Cron::Job.load_from_hash!(schedule, source: "schedule")
9
- elsif schedule.kind_of?(Array)
10
- Sidekiq::Cron::Job.load_from_array!(schedule, source: "schedule")
11
- else
12
- raise "Not supported schedule format. Confirm your #{schedule_file}"
1
+ module Sidekiq
2
+ module Cron
3
+ class ScheduleLoader
4
+ def load_schedule
5
+ if schedule.is_a?(Hash)
6
+ Sidekiq::Cron::Job.load_from_hash!(schedule, source: "schedule")
7
+ elsif schedule.is_a?(Array)
8
+ Sidekiq::Cron::Job.load_from_array!(schedule, source: "schedule")
9
+ else
10
+ raise "Not supported schedule format. Confirm your #{schedule_file_name}"
11
+ end
12
+ end
13
+
14
+ def has_schedule_file?
15
+ File.exist?(schedule_file_name)
16
+ end
17
+
18
+ private
19
+
20
+ def schedule
21
+ @schedule ||= Sidekiq::Cron::Support.load_yaml(rendered_schedule_template)
22
+ end
23
+
24
+ def rendered_schedule_template
25
+ ERB.new(schedule_file_content).result
26
+ end
27
+
28
+ def schedule_file_content
29
+ IO.read(schedule_file_name)
30
+ end
31
+
32
+ def schedule_file_name
33
+ @schedule_file_name ||= yml_to_yaml_unless_file_exists(schedule_file_name_from_config)
34
+ end
35
+
36
+ def schedule_file_name_from_config
37
+ Sidekiq::Cron.configuration.cron_schedule_file
38
+ end
39
+
40
+ def yml_to_yaml_unless_file_exists(file_name)
41
+ if File.exist?(file_name)
42
+ file_name
43
+ else
44
+ file_name.sub(/\.yml$/, ".yaml")
45
+ end
13
46
  end
14
47
  end
15
48
  end
16
49
  end
50
+
51
+ Sidekiq.configure_server do |config|
52
+ config.on(:startup) do
53
+ unless Sidekiq::Cron.configuration.enabled
54
+ Sidekiq.logger.info { "Cron Jobs - skipping schedule loading, Sidekiq-Cron is disabled" }
55
+ next
56
+ end
57
+
58
+ schedule_loader = Sidekiq::Cron::ScheduleLoader.new
59
+ next unless schedule_loader.has_schedule_file?
60
+ schedule_loader.load_schedule
61
+ end
62
+ end
@@ -1,35 +1,10 @@
1
1
  module Sidekiq
2
2
  module Cron
3
3
  module Support
4
- # Inspired by Active Support Inflector
5
- def self.constantize(camel_cased_word)
6
- names = camel_cased_word.split("::".freeze)
7
-
8
- # Trigger a built-in NameError exception including the ill-formed constant in the message.
9
- Object.const_get(camel_cased_word) if names.empty?
10
-
11
- # Remove the first blank element in case of '::ClassName' notation.
12
- names.shift if names.size > 1 && names.first.empty?
13
-
14
- names.inject(Object) do |constant, name|
15
- if constant == Object
16
- constant.const_get(name)
17
- else
18
- candidate = constant.const_get(name)
19
- next candidate if constant.const_defined?(name, false)
20
- next candidate unless Object.const_defined?(name)
21
-
22
- # Go down the ancestors to check if it is owned directly. The check
23
- # stops when we reach Object or the end of ancestors tree.
24
- constant = constant.ancestors.inject(constant) do |const, ancestor|
25
- break const if ancestor == Object
26
- break ancestor if ancestor.const_defined?(name, false)
27
- const
28
- end
29
-
30
- constant.const_get(name, false)
31
- end
32
- end
4
+ def self.safe_constantize(klass_name)
5
+ Object.const_get(klass_name)
6
+ rescue NameError
7
+ nil
33
8
  end
34
9
 
35
10
  def self.load_yaml(src)
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Sidekiq
4
4
  module Cron
5
- VERSION = "2.1.0"
5
+ VERSION = "2.4.0"
6
6
  end
7
7
  end