deployangel 0.1.6 → 0.1.8
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 +4 -4
- data/CHANGELOG.md +21 -0
- data/README.md +98 -2
- data/lib/deployangel/configuration.rb +14 -1
- data/lib/deployangel/rails/http.rb +21 -5
- data/lib/deployangel/rails/metadata.rb +8 -4
- data/lib/deployangel/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 26338624984d9d36012cf0e66b2448ddf31985ae9b629c4ca211e467c5e39e6c
|
|
4
|
+
data.tar.gz: bbbb651d96b7091563b263e20df94bc8471bf132bbf8a0511297176c111a74ed
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 5846a9913395c0defeeb41b1e5f5ad20a924439ee92ca899b09ae76dcbf935748432cf2c02e01b81772029796f39f5cc221c690313b30ea73a2831aac11825d0
|
|
7
|
+
data.tar.gz: 534d6218818cb03b8be1f4144d21d129484b112acdb08a18c53300f62c67dd7198d9dc48a66284ada08069ea08a0795f9cce705d8c6baa11f4fc371d5edf88e5
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,26 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.1.8 (2026-10-04)
|
|
4
|
+
|
|
5
|
+
- Health checks served by a lambda or a mounted Rack app at a conventional
|
|
6
|
+
path (`/up`, `/health`, `/healthz`, `/healthcheck`, `/health_check`,
|
|
7
|
+
`/livez`, `/readyz`, `/statusz`, `/ping`) are left out, like the health-check
|
|
8
|
+
gems'. A controller at one of these paths is still recorded, since it may be
|
|
9
|
+
a real page.
|
|
10
|
+
- `config.sidekiq_cron_schedule_file` reads sidekiq-cron jobs from the file an
|
|
11
|
+
app loads them from itself, such as `config/sidekiq_schedule.yml.erb` passed
|
|
12
|
+
to `Sidekiq::Cron::Job.load_from_hash!`. ERB is rendered, as in the default
|
|
13
|
+
file.
|
|
14
|
+
|
|
15
|
+
## 0.1.7 (2026-10-04)
|
|
16
|
+
|
|
17
|
+
- Health checks from OkComputer, health_check, and rails-healthcheck are left
|
|
18
|
+
out like Rails' own, wherever they're mounted.
|
|
19
|
+
- `config.ignored_routes` leaves out other routes, such as a health check
|
|
20
|
+
served by your own controller: `[ "GET /healthz" ]`. HEAD requests to a
|
|
21
|
+
listed GET route are left out too, and listed routes aren't sent in the
|
|
22
|
+
route table, so DeployAngel never expects them to run.
|
|
23
|
+
|
|
3
24
|
## 0.1.6 (2026-10-04)
|
|
4
25
|
|
|
5
26
|
- Requests to Rails' health check (`rails/health#show`, at `/up` by default)
|
data/README.md
CHANGED
|
@@ -45,8 +45,10 @@ ENV DEPLOYANGEL_REVISION=$GIT_SHA
|
|
|
45
45
|
|
|
46
46
|
and build with `docker build --build-arg GIT_SHA=$(git rev-parse HEAD) .`. On
|
|
47
47
|
DigitalOcean App Platform, set `DEPLOYANGEL_REVISION: ${_self.COMMIT_HASH}` in
|
|
48
|
-
the app spec.
|
|
49
|
-
|
|
48
|
+
the app spec. On Elastic Beanstalk, see
|
|
49
|
+
[Deploying to Elastic Beanstalk](#deploying-to-elastic-beanstalk). If the agent
|
|
50
|
+
finds none of these, its telemetry can't be tied to a deploy, and the dashboard
|
|
51
|
+
says so.
|
|
50
52
|
|
|
51
53
|
## What it sends
|
|
52
54
|
|
|
@@ -73,6 +75,17 @@ deploy, and the dashboard says so.
|
|
|
73
75
|
It does not send request bodies, parameters, headers, cookies, SQL, logs, or
|
|
74
76
|
user data.
|
|
75
77
|
|
|
78
|
+
## Data and pricing
|
|
79
|
+
|
|
80
|
+
- Everything goes to DeployAngel's hosted service, which runs on DigitalOcean
|
|
81
|
+
in the United States. There's no self-hosted version, and no choice of
|
|
82
|
+
region.
|
|
83
|
+
- Telemetry is kept for 21 days, and release history for 7, 30, or 90 days
|
|
84
|
+
depending on the plan. The [privacy policy](https://deployangel.com/privacy)
|
|
85
|
+
has the details and the services DeployAngel uses.
|
|
86
|
+
- DeployAngel is free during the beta, with notice before paid plans start.
|
|
87
|
+
See [pricing](https://deployangel.com/pricing).
|
|
88
|
+
|
|
76
89
|
## Safety
|
|
77
90
|
|
|
78
91
|
- Nothing runs on the network during a request. Requests only update
|
|
@@ -111,6 +124,38 @@ DeployAngel.configure do |config|
|
|
|
111
124
|
end
|
|
112
125
|
```
|
|
113
126
|
|
|
127
|
+
Health checks aren't recorded: load balancers and uptime monitors call them
|
|
128
|
+
all the time and they always answer fast, so they would make your app look
|
|
129
|
+
busier and healthier than its real pages. The agent recognizes Rails' own
|
|
130
|
+
(`/up`), OkComputer, health_check, and rails-healthcheck, wherever they're
|
|
131
|
+
mounted, and a lambda or Rack app at a conventional path (`/up`, `/health`,
|
|
132
|
+
`/healthz`, `/healthcheck`, `/health_check`, `/livez`, `/readyz`, `/statusz`,
|
|
133
|
+
`/ping`), such as `get "healthz", to: ->(_) { [200, {}, ["ok"]] }`. If your
|
|
134
|
+
health check is your own controller, list its route the way the dashboard
|
|
135
|
+
shows it. HEAD requests to it are left out too:
|
|
136
|
+
|
|
137
|
+
```ruby
|
|
138
|
+
DeployAngel.configure do |config|
|
|
139
|
+
config.ignored_routes = [ "GET /healthz" ]
|
|
140
|
+
end
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
DeployAngel expects declared recurring jobs on schedule. It reads Solid Queue's
|
|
144
|
+
`config/recurring.yml`, sidekiq-scheduler's section of Sidekiq's config, and
|
|
145
|
+
sidekiq-cron's schedule file (`config/schedule.yml` unless sidekiq-cron is
|
|
146
|
+
configured otherwise), rendering ERB in each. If your app loads sidekiq-cron
|
|
147
|
+
jobs itself from another file, for example with `Sidekiq::Cron::Job.load_from_hash!`,
|
|
148
|
+
point the agent at that file:
|
|
149
|
+
|
|
150
|
+
```ruby
|
|
151
|
+
DeployAngel.configure do |config|
|
|
152
|
+
config.sidekiq_cron_schedule_file = "config/sidekiq_schedule.yml.erb"
|
|
153
|
+
end
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
Jobs that exist only in Redis, such as ones created in code or in the
|
|
157
|
+
Sidekiq web UI, aren't read.
|
|
158
|
+
|
|
114
159
|
`DEPLOYANGEL_ENABLED=true|false` forces reporting on or off in any environment.
|
|
115
160
|
`DEPLOYANGEL_URL` overrides the API endpoint (default `https://api.deployangel.com`).
|
|
116
161
|
|
|
@@ -169,6 +214,28 @@ to the run. Explicit options still win.
|
|
|
169
214
|
DEPLOYANGEL_API_TOKEN: ${{ secrets.DEPLOYANGEL_API_TOKEN }}
|
|
170
215
|
```
|
|
171
216
|
|
|
217
|
+
### When a release never reports
|
|
218
|
+
|
|
219
|
+
A registered release must start reporting within 15 minutes of being
|
|
220
|
+
registered. If no instance reports it by then, the verdict is
|
|
221
|
+
**inconclusive** with reason `no_release_telemetry`, and the dashboard says
|
|
222
|
+
"Not cleared: this release never reported". The usual causes are a release
|
|
223
|
+
that never booted (a failed migration or a crash on start), an agent with no
|
|
224
|
+
token, or a release identity the agent can't find. If the app kept reporting
|
|
225
|
+
an earlier release instead, the verdict names it ("while v41 kept running").
|
|
226
|
+
`deployangel verify --wait` exits 2, so the CI step above fails the job.
|
|
227
|
+
|
|
228
|
+
If the release does start reporting within 24 hours, as after a slow rolling
|
|
229
|
+
deploy, DeployAngel withdraws that verdict and verifies the release from then,
|
|
230
|
+
unless a newer release has rolled out first. A CI job that already failed stays
|
|
231
|
+
failed; run `deployangel verify --wait` again to wait for the new verdict.
|
|
232
|
+
|
|
233
|
+
Only registered releases are caught this way. DeployAngel learns about an
|
|
234
|
+
unregistered release when the agent first reports it, so a release that never
|
|
235
|
+
boots is never seen. If a release that dies on boot is a risk for you, register
|
|
236
|
+
deploys from CI. The 15 minutes count from registration, so register when the
|
|
237
|
+
deploy finishes, not when it starts.
|
|
238
|
+
|
|
172
239
|
### Deploying with Kamal
|
|
173
240
|
|
|
174
241
|
The agent reads `KAMAL_VERSION`, so it knows its release with no setup. Pass
|
|
@@ -221,6 +288,35 @@ set :deployangel_register, false # turn it off, e.g. for a stage without De
|
|
|
221
288
|
With `:deployangel_wait`, `cap` exits with an error if the release fails
|
|
222
289
|
verification, which CI can act on. It never rolls anything back.
|
|
223
290
|
|
|
291
|
+
### Deploying to Elastic Beanstalk
|
|
292
|
+
|
|
293
|
+
Elastic Beanstalk doesn't tell the app which commit it's running. Set
|
|
294
|
+
`DEPLOYANGEL_REVISION` in the same `update-environment` call that deploys the
|
|
295
|
+
new version, so the two always change together. This works on every platform,
|
|
296
|
+
Docker included, with no build args. Then wait for the environment and register
|
|
297
|
+
the release:
|
|
298
|
+
|
|
299
|
+
```bash
|
|
300
|
+
aws elasticbeanstalk update-environment \
|
|
301
|
+
--environment-name "$EB_ENV" \
|
|
302
|
+
--version-label "$VERSION_LABEL" \
|
|
303
|
+
--option-settings "Namespace=aws:elasticbeanstalk:application:environment,OptionName=DEPLOYANGEL_REVISION,Value=$GITHUB_SHA"
|
|
304
|
+
aws elasticbeanstalk wait environment-updated --environment-names "$EB_ENV"
|
|
305
|
+
|
|
306
|
+
bundle exec deployangel release --commit="$GITHUB_SHA" --version="$VERSION_LABEL"
|
|
307
|
+
bundle exec deployangel verify --wait --until=initial
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
`update-environment` returns as soon as the deploy starts, so don't skip the
|
|
311
|
+
wait: registering early starts the
|
|
312
|
+
[15-minute clock](#when-a-release-never-reports) before the new version is
|
|
313
|
+
running. If the deploy fails and Beanstalk rolls back, the environment still
|
|
314
|
+
returns to Ready, but the new commit never reports, so `verify` exits 2.
|
|
315
|
+
|
|
316
|
+
Leave `DEPLOYANGEL_RELEASE_VERSION` unset here. When the agent reports a
|
|
317
|
+
version, the release is matched by it, so it would have to equal `--version`
|
|
318
|
+
exactly. Matching by commit avoids that.
|
|
319
|
+
|
|
224
320
|
## CLI and coding agents
|
|
225
321
|
|
|
226
322
|
The gem ships a `deployangel` command. It doesn't boot Rails. Give it a token
|
|
@@ -11,7 +11,7 @@ module DeployAngel
|
|
|
11
11
|
|
|
12
12
|
attr_accessor :token, :endpoint, :enabled, :environments, :release_version, :revision,
|
|
13
13
|
:flush_interval, :open_timeout, :read_timeout, :max_queued_payloads, :max_routes, :logger,
|
|
14
|
-
:file_digests, :critical_flows
|
|
14
|
+
:file_digests, :critical_flows, :ignored_routes, :sidekiq_cron_schedule_file
|
|
15
15
|
|
|
16
16
|
def initialize(env = ENV)
|
|
17
17
|
@token = env["DEPLOYANGEL_TOKEN"]
|
|
@@ -28,6 +28,11 @@ module DeployAngel
|
|
|
28
28
|
@logger = Logger.new($stderr, level: Logger::WARN, progname: "deployangel")
|
|
29
29
|
@file_digests = parse_boolean(env["DEPLOYANGEL_FILE_DIGESTS"]) != false
|
|
30
30
|
@critical_flows = {}
|
|
31
|
+
# Route keys as the dashboard shows them, such as "GET /healthz".
|
|
32
|
+
@ignored_routes = []
|
|
33
|
+
# For an app that loads sidekiq-cron's jobs itself (load_from_hash!)
|
|
34
|
+
# from its own file, such as "config/sidekiq_schedule.yml.erb".
|
|
35
|
+
@sidekiq_cron_schedule_file = nil
|
|
31
36
|
end
|
|
32
37
|
|
|
33
38
|
# Reports only with a token. By default only in the listed
|
|
@@ -39,6 +44,14 @@ module DeployAngel
|
|
|
39
44
|
environments.include?(environment.to_s)
|
|
40
45
|
end
|
|
41
46
|
|
|
47
|
+
# Whether requests to a route are left out. Rails answers HEAD with the
|
|
48
|
+
# GET route, so ignoring "GET /healthz" ignores "HEAD /healthz" too.
|
|
49
|
+
def ignored_route?(key)
|
|
50
|
+
return false if ignored_routes.empty?
|
|
51
|
+
|
|
52
|
+
ignored_routes.include?(key) || ignored_routes.include?(key.sub(/\AHEAD /, "GET "))
|
|
53
|
+
end
|
|
54
|
+
|
|
42
55
|
private
|
|
43
56
|
def parse_boolean(value)
|
|
44
57
|
return nil if value.nil? || value.strip.empty?
|
|
@@ -7,10 +7,15 @@ module DeployAngel
|
|
|
7
7
|
# never the raw path, which keeps IDs out of route keys.
|
|
8
8
|
class Http
|
|
9
9
|
FORMAT_SUFFIX = "(.:format)"
|
|
10
|
-
# Rails'
|
|
11
|
-
#
|
|
12
|
-
#
|
|
13
|
-
|
|
10
|
+
# Health checks: Rails' own (/up by default), OkComputer, health_check,
|
|
11
|
+
# and rails-healthcheck, wherever they're mounted. Load balancers and
|
|
12
|
+
# uptime monitors call them all the time and they answer fast, so
|
|
13
|
+
# counting them would make any app look busy and healthy. Others can
|
|
14
|
+
# be left out with config.ignored_routes.
|
|
15
|
+
HEALTH_CHECK_CONTROLLERS = %w[rails/health ok_computer/ok_computer health_check/health_check healthcheck/healthchecks].freeze
|
|
16
|
+
# Health checks served by a lambda or a mounted Rack app rather than a
|
|
17
|
+
# controller, at a conventional path such as /healthz.
|
|
18
|
+
HEALTH_CHECK_PATHS = %w[up health healthz healthcheck health_check livez readyz statusz ping].freeze
|
|
14
19
|
|
|
15
20
|
def initialize(app)
|
|
16
21
|
@app = app
|
|
@@ -35,9 +40,10 @@ module DeployAngel
|
|
|
35
40
|
|
|
36
41
|
private
|
|
37
42
|
def record(env, status, started, unhandled:, exception: nil)
|
|
38
|
-
return if env
|
|
43
|
+
return if health_check?(env)
|
|
39
44
|
|
|
40
45
|
route = route_key(env)
|
|
46
|
+
return if route && DeployAngel.configuration.ignored_route?(route)
|
|
41
47
|
# Unrouted successes are static files and similar; unrouted
|
|
42
48
|
# errors (such as routing 404s) still count.
|
|
43
49
|
return if route.nil? && status.to_i < 400
|
|
@@ -54,6 +60,16 @@ module DeployAngel
|
|
|
54
60
|
nil
|
|
55
61
|
end
|
|
56
62
|
|
|
63
|
+
# A controller at a health-check path may be a real page, so only
|
|
64
|
+
# controllerless routes are judged by their path.
|
|
65
|
+
def health_check?(env)
|
|
66
|
+
controller = env["action_dispatch.request.path_parameters"]&.dig(:controller)
|
|
67
|
+
return HEALTH_CHECK_CONTROLLERS.include?(controller) if controller
|
|
68
|
+
|
|
69
|
+
pattern = route_pattern(env) or return false
|
|
70
|
+
HEALTH_CHECK_PATHS.include?(pattern.delete_suffix(FORMAT_SUFFIX).split("/").last)
|
|
71
|
+
end
|
|
72
|
+
|
|
57
73
|
def route_key(env)
|
|
58
74
|
method = env["REQUEST_METHOD"]
|
|
59
75
|
if (pattern = route_pattern(env))
|
|
@@ -40,12 +40,13 @@ module DeployAngel
|
|
|
40
40
|
@app.routes.routes.flat_map do |route|
|
|
41
41
|
controller = route.defaults[:controller]
|
|
42
42
|
next [] if controller.nil? || controller.start_with?("rails/", "active_storage/", "action_mailbox/")
|
|
43
|
+
next [] if Http::HEALTH_CHECK_CONTROLLERS.include?(controller)
|
|
43
44
|
|
|
44
45
|
path = route.path.spec.to_s.delete_suffix(FORMAT_SUFFIX)
|
|
45
46
|
route.verb.to_s.split("|").reject(&:empty?).map do |verb|
|
|
46
47
|
{ "key" => "#{verb} #{path}", "controller" => controller, "action" => route.defaults[:action].to_s }
|
|
47
48
|
end
|
|
48
|
-
end.uniq { |route| route["key"] }
|
|
49
|
+
end.uniq { |route| route["key"] }.reject { |route| @config.ignored_route?(route["key"]) }
|
|
49
50
|
rescue StandardError
|
|
50
51
|
[]
|
|
51
52
|
end
|
|
@@ -85,8 +86,10 @@ module DeployAngel
|
|
|
85
86
|
end
|
|
86
87
|
|
|
87
88
|
# sidekiq-cron jobs from its schedule file (config/schedule.yml unless
|
|
88
|
-
# configured otherwise), which it loads when Sidekiq starts
|
|
89
|
-
#
|
|
89
|
+
# configured otherwise), which it loads when Sidekiq starts, or from
|
|
90
|
+
# the file in config.sidekiq_cron_schedule_file for an app that loads
|
|
91
|
+
# them itself. Jobs created in code live only in Redis, which the agent
|
|
92
|
+
# doesn't read.
|
|
90
93
|
def sidekiq_cron_schedules
|
|
91
94
|
return [] unless defined?(::Sidekiq::Cron)
|
|
92
95
|
|
|
@@ -104,7 +107,8 @@ module DeployAngel
|
|
|
104
107
|
end
|
|
105
108
|
|
|
106
109
|
def sidekiq_cron_schedule_file
|
|
107
|
-
configured =
|
|
110
|
+
configured = @config.sidekiq_cron_schedule_file
|
|
111
|
+
configured ||= ::Sidekiq::Cron.configuration.cron_schedule_file if ::Sidekiq::Cron.respond_to?(:configuration)
|
|
108
112
|
File.expand_path(configured || "config/schedule.yml", @root)
|
|
109
113
|
rescue StandardError
|
|
110
114
|
File.join(@root, "config", "schedule.yml")
|
data/lib/deployangel/version.rb
CHANGED