deployangel 0.1.4 → 0.1.5

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: 633da2fd342948cb1c2b6f845f05b9a5262d9674a2f7d40f05532995f0751d7e
4
- data.tar.gz: 63327a9c5226d6032f2d7fdc6e432fee07a2cec9bf9d979cd96905b8d362e22c
3
+ metadata.gz: 7d7f056df70ef5a14b11ff88312b8dd9e48330567b9f653cf57d6ddd9caf8050
4
+ data.tar.gz: 7d8c30865b517b59e76cea6cc61d5c0d6853ea7766b41378b790f98983a23d37
5
5
  SHA512:
6
- metadata.gz: 70be2af92e45071c06cf09d60ec43a301db1803eabe917f294927b73d82917283fb9f6cb81af1b6e2daf522b65c52dc9985f1c75c3a16f006157b797c6e2bd15
7
- data.tar.gz: 40518de68db5c3588bccb02e90068dbcc378b342a3ca0325d6a59c253cfa5daead535a54203247d9f58b5910f6d29b3ab2bdec8b23144515141754478bc5417a
6
+ metadata.gz: 4223fbd9e23df830ab6538e927550cbf79057856112427df383da9a77a9a1a0af2f22b5461a4934e1bd945bef60dc8160644632fba1cacd99be0183a29f40295
7
+ data.tar.gz: 790766bea10aa1c12f8e6212fb9d0b10a127f4f7a809bee9434c2952f4ad725c491c20fc2767a606142ec4f9d8d748a50197b6a52861e417f7bb87e0df40a6fa
data/CHANGELOG.md CHANGED
@@ -1,5 +1,18 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.5 (2026-10-03)
4
+
5
+ - Declared recurring jobs from sidekiq-cron and sidekiq-scheduler, alongside
6
+ Solid Queue's. sidekiq-cron jobs come from its schedule file
7
+ (`config/schedule.yml` unless configured otherwise); sidekiq-scheduler jobs
8
+ from the `:scheduler: :schedule:` section of Sidekiq's config file (the
9
+ `-C` file in the Procfile's sidekiq command, else `config/sidekiq.yml`),
10
+ with that environment's section applied. A repeating `every` or `interval`
11
+ is sent as `every`. Both carry the process's local time zone, which the
12
+ schedulers read a schedule in when it names none. Disabled jobs, jobs for
13
+ other environments, and one-off `at` and `in` jobs are left out. Jobs that
14
+ exist only in Redis aren't read.
15
+
3
16
  ## 0.1.4 (2026-10-03)
4
17
 
5
18
  - A Solid Queue recurring task that runs a `command:` instead of a job class
data/README.md CHANGED
@@ -64,9 +64,10 @@ deploy, and the dashboard says so.
64
64
  - Exceptions: a stable fingerprint, the exception class, a sanitized message
65
65
  (numbers, IDs, emails, and quoted values removed), and application frames
66
66
  only.
67
- - Once per process: the route table, job classes, Solid Queue recurring
68
- schedules, critical flows, and file digests (relative paths and hashes, never
69
- file contents) so DeployAngel can tell which routes changed in a release.
67
+ - Once per process: the route table, job classes, recurring schedules
68
+ declared for Solid Queue, sidekiq-cron, or sidekiq-scheduler, critical
69
+ flows, and file digests (relative paths and hashes, never file contents) so
70
+ DeployAngel can tell which routes changed in a release.
70
71
  Disable digests with `DEPLOYANGEL_FILE_DIGESTS=false`.
71
72
 
72
73
  It does not send request bodies, parameters, headers, cookies, SQL, logs, or
@@ -58,8 +58,15 @@ module DeployAngel
58
58
  []
59
59
  end
60
60
 
61
- # Solid Queue recurring tasks for the current environment.
61
+ # Declared recurring jobs from Solid Queue, sidekiq-cron, and
62
+ # sidekiq-scheduler. Each source is read on its own, so a file that
63
+ # can't be read leaves the others' schedules in place.
62
64
  def schedules
65
+ solid_queue_schedules + sidekiq_cron_schedules + sidekiq_scheduler_schedules
66
+ end
67
+
68
+ # Solid Queue recurring tasks for the current environment.
69
+ def solid_queue_schedules
63
70
  path = File.join(@root, "config", "recurring.yml")
64
71
  return [] unless File.file?(path)
65
72
 
@@ -77,6 +84,104 @@ module DeployAngel
77
84
  []
78
85
  end
79
86
 
87
+ # sidekiq-cron jobs from its schedule file (config/schedule.yml unless
88
+ # configured otherwise), which it loads when Sidekiq starts. Jobs
89
+ # created in code live only in Redis, which the agent doesn't read.
90
+ def sidekiq_cron_schedules
91
+ return [] unless defined?(::Sidekiq::Cron)
92
+
93
+ path = yaml_path(sidekiq_cron_schedule_file) or return []
94
+ jobs = load_yaml(path)
95
+ jobs = jobs.map { |name, job| job.is_a?(Hash) ? job.merge("name" => name) : job } if jobs.is_a?(Hash)
96
+ Array(jobs).filter_map do |job|
97
+ next unless job.is_a?(Hash) && job["cron"] && job["status"].to_s != "disabled"
98
+
99
+ { "key" => job["name"].to_s, "class" => (job["klass"] || job["class"])&.to_s, "schedule" => job["cron"].to_s,
100
+ "source" => "sidekiq_cron", "time_zone" => local_time_zone }
101
+ end
102
+ rescue StandardError
103
+ []
104
+ end
105
+
106
+ def sidekiq_cron_schedule_file
107
+ configured = ::Sidekiq::Cron.configuration.cron_schedule_file if ::Sidekiq::Cron.respond_to?(:configuration)
108
+ File.expand_path(configured || "config/schedule.yml", @root)
109
+ rescue StandardError
110
+ File.join(@root, "config", "schedule.yml")
111
+ end
112
+
113
+ # sidekiq-scheduler jobs from Sidekiq's config file, where it reads
114
+ # them under :scheduler: :schedule: (or :schedule: in older versions).
115
+ # A cron runs at set times; every and interval repeat from when the
116
+ # scheduler starts, which a deploy restarts. One-off at and in jobs
117
+ # aren't recurring, so they're left out.
118
+ def sidekiq_scheduler_schedules
119
+ return [] unless defined?(::SidekiqScheduler)
120
+
121
+ path = sidekiq_config_file or return []
122
+ config = normalize_keys(load_yaml(path) || {})
123
+ config = config.merge(config.delete(::Rails.env.to_s) || {}) if config.is_a?(Hash)
124
+ jobs = config.dig("scheduler", "schedule") || config["schedule"]
125
+ return [] unless jobs.is_a?(Hash)
126
+
127
+ jobs.filter_map do |name, job|
128
+ next unless job.is_a?(Hash) && job["enabled"] != false && scheduled_in_this_environment?(job)
129
+
130
+ type = %w[cron every at in interval].find { |key| Array(job[key]).first.to_s.strip != "" }
131
+ next unless %w[cron every interval].include?(type)
132
+
133
+ value = Array(job[type]).first.to_s
134
+ schedule = { "key" => name.to_s, "class" => (job["class"] || name).to_s, "source" => "sidekiq_scheduler",
135
+ "time_zone" => local_time_zone }
136
+ type == "cron" ? schedule.merge("schedule" => value) : schedule.merge("schedule" => nil, "every" => value)
137
+ end
138
+ rescue StandardError
139
+ []
140
+ end
141
+
142
+ # The file Sidekiq loads: the -C path in the Procfile's sidekiq
143
+ # command, else config/sidekiq.yml.
144
+ def sidekiq_config_file
145
+ procfile = File.join(@root, "Procfile")
146
+ named = File.read(procfile)[/\bsidekiq\b[^\n]*?\s(?:-C|--config)[\s=]+(\S+)/, 1] if File.file?(procfile)
147
+ named ? yaml_path(File.expand_path(named, @root)) : yaml_path(File.join(@root, "config", "sidekiq.yml"))
148
+ rescue StandardError
149
+ nil
150
+ end
151
+
152
+ # sidekiq-scheduler skips a job whose rails_env doesn't list this one.
153
+ def scheduled_in_this_environment?(job)
154
+ job["rails_env"].nil? || job["rails_env"].to_s.gsub(/\s/, "").split(",").include?(::Rails.env.to_s)
155
+ end
156
+
157
+ # The zone sidekiq-cron and sidekiq-scheduler read a schedule in when it
158
+ # names none: the process's local zone, as Fugit and rufus-scheduler
159
+ # find it. nil if that can't be told.
160
+ def local_time_zone
161
+ ::EtOrbi.determine_local_tzone&.name if defined?(::EtOrbi)
162
+ rescue StandardError
163
+ nil
164
+ end
165
+
166
+ # The path, or the same name with .yaml if only that exists.
167
+ def yaml_path(path)
168
+ [ path, path.sub(/\.yml\z/, ".yaml") ].find { |candidate| File.file?(candidate) }
169
+ end
170
+
171
+ def load_yaml(path)
172
+ YAML.safe_load(ERB.new(File.read(path), trim_mode: "-").result, permitted_classes: [ Symbol ], aliases: true)
173
+ end
174
+
175
+ # Sidekiq's config uses symbol keys (:scheduler:); compare them as
176
+ # plain strings.
177
+ def normalize_keys(object)
178
+ case object
179
+ when Hash then object.to_h { |key, value| [ key.to_s.delete_prefix(":"), normalize_keys(value) ] }
180
+ when Array then object.map { |value| normalize_keys(value) }
181
+ else object
182
+ end
183
+ end
184
+
80
185
  # A command task runs as a job of this class, so its runs show up in
81
186
  # job telemetry under that name.
82
187
  def command_job_class
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module DeployAngel
4
- VERSION = "0.1.4"
4
+ VERSION = "0.1.5"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: deployangel
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.4
4
+ version: 0.1.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jordan Owens