deployangel 0.1.0 → 0.1.2
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 +20 -0
- data/README.md +13 -5
- data/lib/deployangel/agent.rb +4 -3
- data/lib/deployangel/cli/formatter.rb +3 -0
- data/lib/deployangel/core/transport.rb +19 -9
- data/lib/deployangel/rails/metadata.rb +12 -1
- 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: bd9c6a1966ad36b07d9544c19cfc0b431afd75794ec53f8710792b078e6370ba
|
|
4
|
+
data.tar.gz: 7048de09969cb804f374efbe9e3117402b9e7d048faceb45ad8390707d1e01ac
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 0d11c1b1a206d317f433af474f908fabdc0aae002d36b5977577c5691c9756a0717b2377a4e09815b299ea9c9e096402f74742ca7bb204ae2f2be97bd277b581
|
|
7
|
+
data.tar.gz: 3fa2290369ffdb891d9eb575e4ba90187b3576146a46310e31319352b4ae408b0aae7e1659e5bb45adaf400eea6ca6dab02c032f143d527daf0212a6c5505f17
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,25 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.1.2 (2026-10-02)
|
|
4
|
+
|
|
5
|
+
- Unsent minutes wait in the buffer as gzipped JSON rather than Ruby objects.
|
|
6
|
+
With every list at its cap, 10 minutes queued while DeployAngel was
|
|
7
|
+
unreachable took about 20 MB; they now take well under 1 MB.
|
|
8
|
+
- `rake bench` measures the agent's overhead: time and allocations per
|
|
9
|
+
request, memory with every list at its cap, and the file digest pass.
|
|
10
|
+
|
|
11
|
+
## 0.1.1 (2026-10-02)
|
|
12
|
+
|
|
13
|
+
- Declared Solid Queue schedules carry the time zone Solid Queue reads them
|
|
14
|
+
in (`SolidQueue.time_zone`, which defaults to `config.time_zone`), so
|
|
15
|
+
DeployAngel expects a job scheduled "at 4am every day" at the right hour.
|
|
16
|
+
Without it, a release could fail for a recurring job that wasn't due yet
|
|
17
|
+
when the app's time zone in the dashboard differed from the app's own.
|
|
18
|
+
- `deployangel verify` prints a missing recurring job as "didn't run" and
|
|
19
|
+
when it was expected, instead of its interval as a percentage.
|
|
20
|
+
- The README describes registering deploys on every host before the Heroku
|
|
21
|
+
add-on.
|
|
22
|
+
|
|
3
23
|
## 0.1.0 (2026-10-01)
|
|
4
24
|
|
|
5
25
|
- Release identity on Kamal (`KAMAL_VERSION`), Render (`RENDER_GIT_COMMIT`),
|
data/README.md
CHANGED
|
@@ -78,11 +78,18 @@ user data.
|
|
|
78
78
|
in-memory counters.
|
|
79
79
|
- Payloads are sent from a background thread, once a minute, with short
|
|
80
80
|
timeouts.
|
|
81
|
-
- When DeployAngel is unreachable, the buffer is bounded (10 payloads
|
|
82
|
-
oldest are dropped. Your app is never blocked or failed.
|
|
81
|
+
- When DeployAngel is unreachable, the buffer is bounded (10 payloads, kept as
|
|
82
|
+
gzipped JSON) and the oldest are dropped. Your app is never blocked or failed.
|
|
83
83
|
- Safe across forks (Puma cluster mode and similar), and the minute in progress
|
|
84
84
|
is flushed at shutdown.
|
|
85
85
|
|
|
86
|
+
`bundle exec rake bench` measures this: the time and allocations each request
|
|
87
|
+
adds, memory with every list at its cap, and the one-time file digest pass
|
|
88
|
+
(`APP_ROOT=path/to/app` to digest your own app). On an Apple M-series laptop
|
|
89
|
+
with Ruby 3.4, recording a request adds about 1.6 µs and 6 objects, and with
|
|
90
|
+
every route, job, checkpoint, and exception list at its cap the agent holds
|
|
91
|
+
about 3 MB, including 10 unsent minutes.
|
|
92
|
+
|
|
86
93
|
## Configuration
|
|
87
94
|
|
|
88
95
|
Environment variables are enough for most apps. To override in code:
|
|
@@ -130,9 +137,10 @@ including IDs, since only 100 distinct names are counted per minute.
|
|
|
130
137
|
|
|
131
138
|
## Registering deploys
|
|
132
139
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
140
|
+
DeployAngel notices a new release when the agent first reports it, and
|
|
141
|
+
verifies it from there, with nothing to set up. The releases running when you
|
|
142
|
+
install the agent are the baseline. On Heroku, the add-on also registers every
|
|
143
|
+
release for you.
|
|
136
144
|
|
|
137
145
|
Registering deploys yourself adds a link to the CI run and a label of your
|
|
138
146
|
choice, and starts verification as soon as the deploy finishes. Use an API
|
data/lib/deployangel/agent.rb
CHANGED
|
@@ -97,13 +97,14 @@ module DeployAngel
|
|
|
97
97
|
DeployAngel.capabilities
|
|
98
98
|
end
|
|
99
99
|
|
|
100
|
-
# Drains completed minutes into the buffer
|
|
100
|
+
# Drains completed minutes into the buffer, encoded so unsent minutes stay
|
|
101
|
+
# small while DeployAngel is unreachable, and sends what it can.
|
|
101
102
|
def flush(include_current: false)
|
|
102
103
|
return 0 unless @active
|
|
103
104
|
|
|
104
105
|
@aggregator.drain(include_current: include_current, max_periods: config.max_queued_payloads).each do |period|
|
|
105
|
-
@buffer.push(Protocol.telemetry(period, instance: @instance, release: @release,
|
|
106
|
-
capabilities: capabilities))
|
|
106
|
+
@buffer.push(Transport.encode(Protocol.telemetry(period, instance: @instance, release: @release,
|
|
107
|
+
runtime: @runtime, capabilities: capabilities)))
|
|
107
108
|
end
|
|
108
109
|
send_buffered
|
|
109
110
|
rescue StandardError => e
|
|
@@ -70,6 +70,9 @@ module DeployAngel
|
|
|
70
70
|
label = "#{LABELS.fetch(finding["signal"], finding["signal"])} on #{finding["scope"]}"
|
|
71
71
|
if COUNTS.include?(finding["signal"])
|
|
72
72
|
"#{label}: #{finding["observed_value"].to_i} occurrences"
|
|
73
|
+
elsif finding["signal"] == "missing_recurring_job"
|
|
74
|
+
# Its value is the job's interval in seconds, not a rate.
|
|
75
|
+
"#{label}: #{finding["status"] == "pass" ? "ran" : "didn't run"}, #{finding["threshold"]}"
|
|
73
76
|
elsif finding["baseline_value"].nil? && finding["observed_value"].nil?
|
|
74
77
|
"#{label}: #{finding["threshold"]}"
|
|
75
78
|
else
|
|
@@ -16,10 +16,28 @@ module DeployAngel
|
|
|
16
16
|
end
|
|
17
17
|
end
|
|
18
18
|
|
|
19
|
+
# A payload already encoded as gzipped JSON. Queued telemetry is kept
|
|
20
|
+
# this way: a full minute is megabytes as Ruby objects but kilobytes
|
|
21
|
+
# compressed.
|
|
22
|
+
Encoded = Struct.new(:bytes)
|
|
23
|
+
|
|
24
|
+
def self.encode(body)
|
|
25
|
+
Encoded.new(gzip(JSON.generate(body)))
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def self.gzip(string)
|
|
29
|
+
io = StringIO.new
|
|
30
|
+
writer = Zlib::GzipWriter.new(io)
|
|
31
|
+
writer.write(string)
|
|
32
|
+
writer.close
|
|
33
|
+
io.string
|
|
34
|
+
end
|
|
35
|
+
|
|
19
36
|
def initialize(config)
|
|
20
37
|
@config = config
|
|
21
38
|
end
|
|
22
39
|
|
|
40
|
+
# body is a Hash, or an Encoded payload sent as is.
|
|
23
41
|
# :ok (2xx), :retry (network error or 5xx), or :drop (any other status,
|
|
24
42
|
# including 429 rate limiting, which the agent honors by pausing).
|
|
25
43
|
def post(path, body)
|
|
@@ -29,7 +47,7 @@ module DeployAngel
|
|
|
29
47
|
request["Content-Type"] = "application/json"
|
|
30
48
|
request["Content-Encoding"] = "gzip"
|
|
31
49
|
request["User-Agent"] = "deployangel-ruby/#{DeployAngel::VERSION} ruby/#{RUBY_VERSION}"
|
|
32
|
-
request.body =
|
|
50
|
+
request.body = (body.is_a?(Encoded) ? body : self.class.encode(body)).bytes
|
|
33
51
|
|
|
34
52
|
response = Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == "https",
|
|
35
53
|
open_timeout: @config.open_timeout, read_timeout: @config.read_timeout,
|
|
@@ -54,13 +72,5 @@ module DeployAngel
|
|
|
54
72
|
rescue JSON::ParserError
|
|
55
73
|
{}
|
|
56
74
|
end
|
|
57
|
-
|
|
58
|
-
def gzip(string)
|
|
59
|
-
io = StringIO.new
|
|
60
|
-
writer = Zlib::GzipWriter.new(io)
|
|
61
|
-
writer.write(string)
|
|
62
|
-
writer.close
|
|
63
|
-
io.string
|
|
64
|
-
end
|
|
65
75
|
end
|
|
66
76
|
end
|
|
@@ -68,12 +68,23 @@ module DeployAngel
|
|
|
68
68
|
Array(tasks).filter_map do |key, task|
|
|
69
69
|
next unless task.is_a?(Hash) && task["schedule"]
|
|
70
70
|
|
|
71
|
-
{ "key" => key.to_s, "class" => task["class"]&.to_s, "schedule" => task["schedule"].to_s, "source" => "solid_queue"
|
|
71
|
+
{ "key" => key.to_s, "class" => task["class"]&.to_s, "schedule" => task["schedule"].to_s, "source" => "solid_queue",
|
|
72
|
+
"time_zone" => scheduler_time_zone }
|
|
72
73
|
end
|
|
73
74
|
rescue StandardError
|
|
74
75
|
[]
|
|
75
76
|
end
|
|
76
77
|
|
|
78
|
+
# The zone Solid Queue reads a schedule in when the schedule names
|
|
79
|
+
# none: its own setting, config.time_zone by default. nil when that's
|
|
80
|
+
# the system's local time or Solid Queue predates the setting, and
|
|
81
|
+
# DeployAngel then doesn't assume one.
|
|
82
|
+
def scheduler_time_zone
|
|
83
|
+
::SolidQueue.time_zone if defined?(::SolidQueue) && ::SolidQueue.respond_to?(:time_zone)
|
|
84
|
+
rescue StandardError
|
|
85
|
+
nil
|
|
86
|
+
end
|
|
87
|
+
|
|
77
88
|
def file_manifest
|
|
78
89
|
@file_manifest ||= begin
|
|
79
90
|
if @config.file_digests
|
data/lib/deployangel/version.rb
CHANGED