cronwatch 0.14.0 → 0.14.1
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/lib/cronwatch/client.rb +8 -5
- data/lib/cronwatch/job.rb +20 -8
- data/lib/cronwatch/version.rb +1 -1
- data/lib/cronwatch/web/app.rb +7 -3
- 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: d0a9fcfe8eb1b18e8afd7b80e2b41e62569bf606db798252d2305f9f353235ff
|
|
4
|
+
data.tar.gz: 12626c9fc256348658a3843ac1704b97ef7cd4fde62f289e0c570c174dd29704
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 4d9274543edac3d3400762e5863b08a2b9cf8a818b134996e28a292cfe802552e6ecffbc68b18a7a4a9258499e476e7e7982a546b4b204ebc5f146829e14cebc
|
|
7
|
+
data.tar.gz: ab1c1ef624f8e94f56eb73c2be364ebf9b799e1b7bceef3a69e4398604e72c1eb63252f894aec82bdb4dfc61d9b9803048a02564dd60d807a273cfde62b0b364
|
data/lib/cronwatch/client.rb
CHANGED
|
@@ -428,7 +428,7 @@ module Cronwatch
|
|
|
428
428
|
update_state(run.job) { |before| [Evaluate.hold_run(Evaluate.on_run_start(definition, run, before, now), run.id), nil] }
|
|
429
429
|
return [] if run.status == :running
|
|
430
430
|
|
|
431
|
-
finish_run(definition, run, now)
|
|
431
|
+
finish_run(definition, run, now, pending_only: true)
|
|
432
432
|
end
|
|
433
433
|
|
|
434
434
|
# Hands an error to on_error, as a source reports what went wrong. See #report.
|
|
@@ -919,6 +919,7 @@ module Cronwatch
|
|
|
919
919
|
end
|
|
920
920
|
return [] if !%i[running timeout].include?(stored.status) || run.status == :running
|
|
921
921
|
|
|
922
|
+
tracked = (read_state(run.job).pending_runs || []).include?(run.id)
|
|
922
923
|
late, ignored = claim_finish(run)
|
|
923
924
|
if ignored
|
|
924
925
|
report(RuntimeError.new("run #{run.id} of #{run.job} #{ignored}; ignored"), "recording #{run.job}")
|
|
@@ -931,7 +932,7 @@ module Cronwatch
|
|
|
931
932
|
end
|
|
932
933
|
return [] if late && run.status != :ok
|
|
933
934
|
|
|
934
|
-
finish_run(definition, run, now)
|
|
935
|
+
finish_run(definition, run, now, pending_only: tracked && !late)
|
|
935
936
|
end
|
|
936
937
|
|
|
937
938
|
# Takes a run stored without judging out of the job's pending runs, so a
|
|
@@ -1028,13 +1029,14 @@ module Cronwatch
|
|
|
1028
1029
|
return "belongs to job \"#{stored.job}\"" if stored.job != run.job
|
|
1029
1030
|
end
|
|
1030
1031
|
end
|
|
1032
|
+
tracked = (read_state(run.job).pending_runs || []).include?(run.id)
|
|
1031
1033
|
late, ignored = claim_finish(run)
|
|
1032
1034
|
return ignored if ignored
|
|
1033
1035
|
|
|
1034
1036
|
if !judge
|
|
1035
1037
|
release_unjudged(run)
|
|
1036
1038
|
elsif !late || run.status == :ok
|
|
1037
|
-
finish_run(Serialize.to_stored(definition), run, finished_at)
|
|
1039
|
+
finish_run(Serialize.to_stored(definition), run, finished_at, pending_only: tracked && !late)
|
|
1038
1040
|
end
|
|
1039
1041
|
nil
|
|
1040
1042
|
end
|
|
@@ -1250,7 +1252,7 @@ module Cronwatch
|
|
|
1250
1252
|
end
|
|
1251
1253
|
next if run.status == :running || !run.finished_at.is_a?(Numeric) || run.finished_at > at - Evaluate::SEND_LEASE_MS
|
|
1252
1254
|
|
|
1253
|
-
alerts.concat(finish_run(stored.definition, run, at))
|
|
1255
|
+
alerts.concat(finish_run(stored.definition, run, at, pending_only: true))
|
|
1254
1256
|
rescue StandardError => e
|
|
1255
1257
|
report(e, "evaluating run #{id} of #{stored.name}")
|
|
1256
1258
|
end
|
|
@@ -1260,11 +1262,12 @@ module Cronwatch
|
|
|
1260
1262
|
# Evaluate a finished run (ok, failed, or timed out by a check), already
|
|
1261
1263
|
# written, against the job's state and send what that produces. The
|
|
1262
1264
|
# alerts are written with that state (see outbox). Never raises.
|
|
1263
|
-
def finish_run(definition, run, at)
|
|
1265
|
+
def finish_run(definition, run, at, pending_only: false)
|
|
1264
1266
|
held = nil
|
|
1265
1267
|
begin
|
|
1266
1268
|
past = nil
|
|
1267
1269
|
_, held = update_state(run.job) do |previous|
|
|
1270
|
+
next [previous, Outbox.new([], 0)] if pending_only && !(previous.pending_runs || []).include?(run.id)
|
|
1268
1271
|
past ||= history(run)
|
|
1269
1272
|
before = Evaluate.release_run(previous, run.id)
|
|
1270
1273
|
outbox(Evaluate.apply_silence(before, Evaluate.on_run_finish(definition, run, before, past, at), at), definition, at)
|
data/lib/cronwatch/job.rb
CHANGED
|
@@ -100,7 +100,7 @@ module Cronwatch
|
|
|
100
100
|
class RunRecorder
|
|
101
101
|
# Lines are dropped from the front once the output is well past the cap;
|
|
102
102
|
# Output.redact_and_cap trims it exactly at the end.
|
|
103
|
-
KEEP =
|
|
103
|
+
KEEP = Output::CAP + Output::REDACT_EDGE + 2
|
|
104
104
|
|
|
105
105
|
attr_reader :context, :signal
|
|
106
106
|
|
|
@@ -111,7 +111,9 @@ module Cronwatch
|
|
|
111
111
|
# dropped from @lines: what expect_text needs once the output runs long.
|
|
112
112
|
@head = []
|
|
113
113
|
@head_size = 0
|
|
114
|
-
@
|
|
114
|
+
@whole = ""
|
|
115
|
+
@tail = ""
|
|
116
|
+
@logged = false
|
|
115
117
|
@metrics = {}
|
|
116
118
|
@lock = Mutex.new
|
|
117
119
|
@signal = AbortSignal.new(timeout_ms)
|
|
@@ -121,15 +123,26 @@ module Cronwatch
|
|
|
121
123
|
def log(line)
|
|
122
124
|
length = JS.length16(line)
|
|
123
125
|
@lock.synchronize do
|
|
126
|
+
separator = @logged ? "\n" : ""
|
|
127
|
+
@logged = true
|
|
128
|
+
if @whole
|
|
129
|
+
@whole = JS.length16(@whole) + separator.length + length <= 2 * Output::CAP ? @whole + separator + line : nil
|
|
130
|
+
end
|
|
131
|
+
@tail = JS.tail16(@tail + separator + JS.tail16(line, Output::CAP), Output::CAP).dup
|
|
124
132
|
if @head_size < Output::CAP
|
|
125
|
-
@head << line
|
|
133
|
+
@head << JS.head16(line, Output::CAP - @head_size).dup
|
|
126
134
|
@head_size += length + 1
|
|
127
135
|
end
|
|
136
|
+
line = Output.strip_nul(line)
|
|
137
|
+
length = JS.length16(line)
|
|
138
|
+
if length >= KEEP
|
|
139
|
+
line = JS.tail16(line, KEEP - 1).dup
|
|
140
|
+
length = JS.length16(line)
|
|
141
|
+
end
|
|
128
142
|
@lines << line
|
|
129
143
|
@size += length + 1
|
|
130
144
|
while @size > KEEP && @lines.length > 1
|
|
131
145
|
@size -= JS.length16(@lines.shift) + 1
|
|
132
|
-
@dropped = true
|
|
133
146
|
end
|
|
134
147
|
end
|
|
135
148
|
end
|
|
@@ -138,7 +151,7 @@ module Cronwatch
|
|
|
138
151
|
@lock.synchronize { @metrics[name] = value }
|
|
139
152
|
end
|
|
140
153
|
|
|
141
|
-
# The lines still held (past
|
|
154
|
+
# The lines still held (past the bounded redaction window the oldest are let go), joined and not
|
|
142
155
|
# yet capped: the client redacts them first, then caps them
|
|
143
156
|
# (Output.redact_and_cap).
|
|
144
157
|
def output
|
|
@@ -152,10 +165,9 @@ module Cronwatch
|
|
|
152
165
|
@lock.synchronize do
|
|
153
166
|
next nil if @lines.empty?
|
|
154
167
|
|
|
155
|
-
|
|
156
|
-
next all if !@dropped && JS.length16(all) <= 2 * Output::CAP
|
|
168
|
+
next @whole if @whole
|
|
157
169
|
|
|
158
|
-
"#{JS.head16(@head.join("\n"), Output::CAP)}\n#{
|
|
170
|
+
"#{JS.head16(@head.join("\n"), Output::CAP)}\n#{@tail}"
|
|
159
171
|
end
|
|
160
172
|
end
|
|
161
173
|
|
data/lib/cronwatch/version.rb
CHANGED
data/lib/cronwatch/web/app.rb
CHANGED
|
@@ -264,6 +264,7 @@ module Cronwatch
|
|
|
264
264
|
# the URL and every access log. Cross-site posts were refused above.
|
|
265
265
|
if @token && method == "POST" && path == "/signin"
|
|
266
266
|
body = read_body(request)
|
|
267
|
+
return html(HTML.message_page("Bad request", "Malformed request body", base), 400) if body.equal?(BAD_BODY)
|
|
267
268
|
# A body too large is no token at all.
|
|
268
269
|
sent = body.equal?(TOO_LARGE) ? nil : body["token"]
|
|
269
270
|
return sign_in_page(base) if sent.nil? || !HTTP.constant_time_equal?(sent, @token)
|
|
@@ -341,6 +342,7 @@ module Cronwatch
|
|
|
341
342
|
if action == "silence"
|
|
342
343
|
body = read_body(request)
|
|
343
344
|
return html(HTML.message_page("Not silenced", "The request was too large.", base), 413) if body.equal?(TOO_LARGE)
|
|
345
|
+
return html(HTML.message_page("Not silenced", "Malformed request body", base), 400) if body.equal?(BAD_BODY)
|
|
344
346
|
|
|
345
347
|
begin
|
|
346
348
|
duration = silence_duration(body["for"])
|
|
@@ -388,6 +390,7 @@ module Cronwatch
|
|
|
388
390
|
if rest[2] == "silence"
|
|
389
391
|
body = read_body(request)
|
|
390
392
|
return api({ ok: false, error: "Request body too large" }, 413) if body.equal?(TOO_LARGE)
|
|
393
|
+
return api({ ok: false, error: "Malformed request body" }, 400) if body.equal?(BAD_BODY)
|
|
391
394
|
|
|
392
395
|
begin
|
|
393
396
|
duration = silence_duration(body.key?("for") ? body["for"] : request.query("for"))
|
|
@@ -530,7 +533,8 @@ module Cronwatch
|
|
|
530
533
|
|
|
531
534
|
# A request body past Request::MAX_BODY, which read_body answers with.
|
|
532
535
|
TOO_LARGE = Object.new.freeze
|
|
533
|
-
|
|
536
|
+
BAD_BODY = Object.new.freeze
|
|
537
|
+
private_constant :TOO_LARGE, :BAD_BODY
|
|
534
538
|
|
|
535
539
|
# The form fields or JSON object of a request, each value as String(value)
|
|
536
540
|
# gives it in JavaScript, or TOO_LARGE for a body past Request::MAX_BODY.
|
|
@@ -546,13 +550,13 @@ module Cronwatch
|
|
|
546
550
|
if json
|
|
547
551
|
data = JSON.parse(Output.utf8(request.body).delete_prefix(""))
|
|
548
552
|
return data.to_h { |k, v| [k.to_s, js_string(v)] } if data.is_a?(Hash)
|
|
549
|
-
return
|
|
553
|
+
return BAD_BODY
|
|
550
554
|
else
|
|
551
555
|
return request.form.transform_values { |v| form_value(v) }
|
|
552
556
|
end
|
|
553
557
|
{}
|
|
554
558
|
rescue StandardError
|
|
555
|
-
|
|
559
|
+
BAD_BODY
|
|
556
560
|
end
|
|
557
561
|
|
|
558
562
|
# String(value) for a parsed JSON value.
|