deployangel 0.1.20 → 0.1.21
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 +6 -0
- data/README.md +3 -2
- data/lib/deployangel/exerciser.rb +40 -16
- 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: 2a115a5a35d86c5edce65dad1cdf3947833416ea66f47c69da18b85de3047153
|
|
4
|
+
data.tar.gz: ec954e62605c6452d3e374896085343cb6a12910e0b821794342a2e0a6dc0426
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 4c18ca61ecff8936bdfa7f4a5900fd27b96c2a787094e504593fae78f62eaee3c7d4ea11e74811b619313c1a726bce8e4644f5a6319c687450ff81629acdd2a5
|
|
7
|
+
data.tar.gz: 2754b3cc315c626df0dc57de4723721caa668f69b7b523a0ca4eaecaf4f84e2f2ec7f9f40f366be34f9a1989ff3c9b4650f1d02dc81b976218d2b3daf5e3968c
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.1.21 (2026-10-09)
|
|
4
|
+
|
|
5
|
+
- `deployangel exercise` stops requesting a page after its first 404, 405,
|
|
6
|
+
or 410, since a route table can list pages an app doesn't serve, and sends
|
|
7
|
+
that page's share to the pages that answered.
|
|
8
|
+
|
|
3
9
|
## 0.1.20 (2026-10-09)
|
|
4
10
|
|
|
5
11
|
- `deployangel exercise --url=<production URL>` sends a release's exercise
|
data/README.md
CHANGED
|
@@ -471,8 +471,9 @@ path parameters, spreading any request shortfall across them (at most 200
|
|
|
471
471
|
requests, about 5 a second, as `DeployAngel-Exercise`), then records what it
|
|
472
472
|
sent on the release, where the page lists it under "Exercised from your
|
|
473
473
|
side". It skips and names routes that change data or need a path
|
|
474
|
-
parameter
|
|
475
|
-
|
|
474
|
+
parameter, and stops requesting a page after its first 404, sending its
|
|
475
|
+
share to the pages that answered. A "CLI & coding agents" token can run
|
|
476
|
+
it. `--dry-run` shows what it would send.
|
|
476
477
|
|
|
477
478
|
### Suggested instructions for your coding agent
|
|
478
479
|
|
|
@@ -16,6 +16,9 @@ module DeployAngel
|
|
|
16
16
|
TIMEOUT = 10
|
|
17
17
|
# Stop when the app isn't answering, rather than send the rest into it.
|
|
18
18
|
MAX_CONSECUTIVE_ERRORS = 5
|
|
19
|
+
# A route table can list pages an app doesn't serve, like the edit page
|
|
20
|
+
# of a resource that has none. One answer like this is enough to know.
|
|
21
|
+
NOT_SERVED = [ 404, 405, 410 ].freeze
|
|
19
22
|
# Rails' :id and *path, Django's <int:pk>, FastAPI's {id}.
|
|
20
23
|
PARAMETER = /\/[:*{<]/
|
|
21
24
|
|
|
@@ -51,27 +54,34 @@ module DeployAngel
|
|
|
51
54
|
[ targets, skipped ]
|
|
52
55
|
end
|
|
53
56
|
|
|
57
|
+
# A route whose first request says it isn't served gets no more; its
|
|
58
|
+
# share goes to the routes that answered.
|
|
54
59
|
def run(exercise_plan)
|
|
55
60
|
targets, skipped = plan(exercise_plan)
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
61
|
+
@statuses = Hash.new { |hash, key| hash[key] = Hash.new(0) }
|
|
62
|
+
@sent = 0
|
|
63
|
+
@errors_in_a_row = 0
|
|
64
|
+
leftover = 0
|
|
65
|
+
answered = []
|
|
59
66
|
targets.each do |target|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
statuses[kind] += 1
|
|
69
|
-
errors_in_a_row = kind == "error" ? errors_in_a_row + 1 : 0
|
|
67
|
+
target.count.times do |i|
|
|
68
|
+
break if unreachable?
|
|
69
|
+
|
|
70
|
+
status = send_request(target)
|
|
71
|
+
if i.zero? && NOT_SERVED.include?(status)
|
|
72
|
+
leftover += target.count - 1
|
|
73
|
+
break
|
|
74
|
+
end
|
|
70
75
|
end
|
|
71
|
-
|
|
72
|
-
|
|
76
|
+
answered << target if @statuses[target.key].keys.intersect?(%w[2xx 3xx])
|
|
77
|
+
end
|
|
78
|
+
leftover.times { |i| send_request(answered[i % answered.size]) unless unreachable? } if answered.any?
|
|
79
|
+
|
|
80
|
+
routes = targets.filter_map do |target|
|
|
81
|
+
statuses = @statuses.fetch(target.key, nil) or next
|
|
82
|
+
{ "key" => target.key, "requests" => statuses.values.sum, "statuses" => statuses.to_h }
|
|
73
83
|
end
|
|
74
|
-
Result.new(routes: routes, skipped: skipped, sent: sent, unreachable:
|
|
84
|
+
Result.new(routes: routes, skipped: skipped, sent: @sent, unreachable: unreachable?)
|
|
75
85
|
end
|
|
76
86
|
|
|
77
87
|
def url_for(path)
|
|
@@ -82,6 +92,20 @@ module DeployAngel
|
|
|
82
92
|
end
|
|
83
93
|
|
|
84
94
|
private
|
|
95
|
+
def send_request(target)
|
|
96
|
+
@sleeper.call(INTERVAL) if @sent.positive?
|
|
97
|
+
status = @requester.call(url_for(target.path))
|
|
98
|
+
@sent += 1
|
|
99
|
+
kind = status ? "#{status / 100}xx" : "error"
|
|
100
|
+
@statuses[target.key][kind] += 1
|
|
101
|
+
@errors_in_a_row = kind == "error" ? @errors_in_a_row + 1 : 0
|
|
102
|
+
status
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def unreachable?
|
|
106
|
+
@errors_in_a_row >= MAX_CONSECUTIVE_ERRORS
|
|
107
|
+
end
|
|
108
|
+
|
|
85
109
|
# Requests the release is short of overall, spread across the routes,
|
|
86
110
|
# or sent to the home page when the plan names no route to send them to.
|
|
87
111
|
def spread_shortfall(targets, requests)
|
data/lib/deployangel/version.rb
CHANGED