watchfor 0.5.0 → 0.7.0
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/README.md +37 -2
- data/exe/watchfor +5 -2
- data/lib/watchfor/client.rb +54 -3
- data/lib/watchfor/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 0fa3724c68935a0f14be699f9b232640b29b75811132dcf9e96cdec986d17132
|
|
4
|
+
data.tar.gz: 27b90a62ac79058fb401d4c0f75e7fbb59bc0c5622cb33b41fed8bb71ea49d2d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 8fdfa7930ac0000827e8f5325e08bd5e99a10ec8ef0409b04144273e284cd16cbb6f1c9f37aaa2fda22df74998711c860bf1b93ac4e53136ca5acd5cd4daf114
|
|
7
|
+
data.tar.gz: 0a326ed15feab7394d1ab2f159cdab8a3bc2f402f3b47c0302dc8b1b5868ccfbe749860ca5ac70f83f5130d8f21a6248f4370df738f33a8cd220a73f05c016f4
|
data/README.md
CHANGED
|
@@ -39,10 +39,45 @@ wf.incidents.exclude(19351, reason: "Probe-side DNS hiccup")
|
|
|
39
39
|
|
|
40
40
|
# Irreversible: wipe a monitor's checks and incidents; uptime restarts "since reset"
|
|
41
41
|
wf.monitors.reset(mon["id"])
|
|
42
|
+
|
|
43
|
+
# Older checks: pass next_cursor back as cursor (keep the same hours/success)
|
|
44
|
+
page = wf.monitors.checks(mon["id"], hours: 24, limit: 100)
|
|
45
|
+
wf.monitors.checks(mon["id"], hours: 24, limit: 100, cursor: page["next_cursor"]) if page["next_cursor"]
|
|
46
|
+
|
|
47
|
+
# Delete a contact group that alert rules still use (409 without force)
|
|
48
|
+
wf.contact_groups.delete("<group-id>", force: true)
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### Live diagnostics
|
|
52
|
+
|
|
53
|
+
Run any of 18 checks from WatchFor's probe fleet against **any public
|
|
54
|
+
target**, monitored or not — this measures right now, rather than reading
|
|
55
|
+
what WatchFor recorded:
|
|
56
|
+
|
|
57
|
+
```ruby
|
|
58
|
+
# What can I run, and how much budget is left?
|
|
59
|
+
catalog = wf.diagnostics.list
|
|
60
|
+
|
|
61
|
+
# One check, optionally from a location you choose (plan-gated)
|
|
62
|
+
dns = wf.diagnostics.run("dns-lookup", "example.com",
|
|
63
|
+
options: { recordType: "A", resolver: "8.8.8.8" })
|
|
64
|
+
# A closed port / NXDOMAIN / failed handshake does NOT raise:
|
|
65
|
+
puts "finding: #{dns['error']}" unless dns["success"]
|
|
66
|
+
puts "runs left this hour: #{dns['remaining']}"
|
|
67
|
+
|
|
68
|
+
# The whole picture in one call: DNS + propagation + TLS + HTTP + ping
|
|
69
|
+
# from up to 3 regions, aggregated into one verdict
|
|
70
|
+
report = wf.diagnostics.diagnose_target("example.com")
|
|
71
|
+
puts "#{report['verdict']['status']}: #{report['verdict']['summary']}"
|
|
42
72
|
```
|
|
43
73
|
|
|
74
|
+
Runs need a `write` key (a probe sends real traffic from WatchFor's IPs)
|
|
75
|
+
and spend the same per-plan hourly allowance as the dashboard Toolbox.
|
|
76
|
+
Details: <https://watchfor.io/docs/api/diagnostics>
|
|
77
|
+
|
|
44
78
|
Namespaces: `wf.monitors`, `wf.alert_rules`, `wf.incidents`,
|
|
45
|
-
`wf.maintenance_windows`, `wf.contacts`, `wf.contact_groups
|
|
79
|
+
`wf.maintenance_windows`, `wf.contacts`, `wf.contact_groups`,
|
|
80
|
+
`wf.diagnostics`. Top-level:
|
|
46
81
|
`wf.summary`, `wf.plan`, `wf.me`, `wf.locations`, `wf.monitor_types`,
|
|
47
82
|
`wf.incident_stats(period)`, `wf.notifications`, `wf.activity`. Errors raise
|
|
48
83
|
`Watchfor::Error` with `#status`, `#code`, `#message`.
|
|
@@ -54,7 +89,7 @@ export WATCHFOR_API_KEY=wf_live_...
|
|
|
54
89
|
watchfor summary
|
|
55
90
|
watchfor monitors
|
|
56
91
|
watchfor incidents
|
|
57
|
-
watchfor checks <monitor_id>
|
|
92
|
+
watchfor checks <monitor_id> [--cursor <next_cursor>]
|
|
58
93
|
watchfor report --period 30d
|
|
59
94
|
```
|
|
60
95
|
|
data/exe/watchfor
CHANGED
|
@@ -31,10 +31,13 @@ begin
|
|
|
31
31
|
when "locations" then wf.locations
|
|
32
32
|
when "monitor-types" then wf.monitor_types
|
|
33
33
|
when "incidents" then wf.incidents.list
|
|
34
|
-
when "checks"
|
|
34
|
+
when "checks"
|
|
35
|
+
monitor_id = args.shift
|
|
36
|
+
cursor = args.include?("--cursor") ? args[args.index("--cursor") + 1] : nil
|
|
37
|
+
wf.monitors.checks(monitor_id, cursor: cursor)
|
|
35
38
|
when "report" then wf.report(period: args.include?("--period") ? args[args.index("--period") + 1] : nil)
|
|
36
39
|
else
|
|
37
|
-
warn "Usage: watchfor [summary|plan|monitors|locations|monitor-types|incidents|checks <monitor_id
|
|
40
|
+
warn "Usage: watchfor [summary|plan|monitors|locations|monitor-types|incidents|checks <monitor_id> [--cursor c]|report [--period 7d|30d]]"
|
|
38
41
|
exit 2
|
|
39
42
|
end
|
|
40
43
|
puts JSON.pretty_generate(result)
|
data/lib/watchfor/client.rb
CHANGED
|
@@ -26,7 +26,8 @@ module Watchfor
|
|
|
26
26
|
# wf.monitors.list["data"].each { |m| puts "#{m['name']} #{m['status']}" }
|
|
27
27
|
class Client
|
|
28
28
|
attr_reader :monitors, :alert_rules, :incidents,
|
|
29
|
-
:maintenance_windows, :contacts, :contact_groups
|
|
29
|
+
:maintenance_windows, :contacts, :contact_groups,
|
|
30
|
+
:diagnostics
|
|
30
31
|
|
|
31
32
|
def initialize(api_key:, base_url: DEFAULT_BASE_URL, timeout: 30)
|
|
32
33
|
raise ArgumentError, "api_key is required" if api_key.nil? || api_key.empty?
|
|
@@ -40,6 +41,7 @@ module Watchfor
|
|
|
40
41
|
@maintenance_windows = MaintenanceWindows.new(self)
|
|
41
42
|
@contacts = Contacts.new(self)
|
|
42
43
|
@contact_groups = ContactGroups.new(self)
|
|
44
|
+
@diagnostics = Diagnostics.new(self)
|
|
43
45
|
end
|
|
44
46
|
|
|
45
47
|
# Core request. +query+ and +body+ are optional hashes.
|
|
@@ -113,19 +115,26 @@ module Watchfor
|
|
|
113
115
|
|
|
114
116
|
class Monitors < Namespace
|
|
115
117
|
def list(**query) = @c.request("GET", "/monitors", query: query)
|
|
118
|
+
# The monitor plus available_metrics (alert metrics valid for its type).
|
|
116
119
|
def get(id) = @c.request("GET", "/monitors/#{id}")
|
|
117
120
|
def create(body, idempotency_key: nil) = @c.request("POST", "/monitors", body: body, idempotency_key: idempotency_key)
|
|
118
121
|
def update(id, body) = @c.request("PATCH", "/monitors/#{id}", body: body)
|
|
119
122
|
def delete(id) = @c.request("DELETE", "/monitors/#{id}")
|
|
120
123
|
def pause(id) = @c.request("POST", "/monitors/#{id}/pause")
|
|
121
124
|
def resume(id) = @c.request("POST", "/monitors/#{id}/resume")
|
|
125
|
+
# Dispatch one out-of-schedule check now (202); the result lands in #checks moments later.
|
|
122
126
|
def check_now(id) = @c.request("POST", "/monitors/#{id}/check-now")
|
|
123
127
|
# IRREVERSIBLE: deletes every check, incident, note and post-mortem of the
|
|
124
128
|
# monitor and starts uptime over ("since reset"); the monitor and its
|
|
125
129
|
# configuration, alert rules, tags and locations stay.
|
|
126
130
|
def reset(id) = @c.request("POST", "/monitors/#{id}/reset")
|
|
127
131
|
def uptime(id, period = nil) = @c.request("GET", "/monitors/#{id}/uptime", query: { period: period })
|
|
128
|
-
|
|
132
|
+
# Recent probe results, newest first. Page with the response's next_cursor -> cursor:
|
|
133
|
+
# (keep the same hours/success); "locations" is the per-location breakdown for the whole window.
|
|
134
|
+
def checks(id, hours: nil, limit: nil, success: nil, cursor: nil, **query)
|
|
135
|
+
@c.request("GET", "/monitors/#{id}/checks",
|
|
136
|
+
query: { hours: hours, limit: limit, success: success, cursor: cursor, **query })
|
|
137
|
+
end
|
|
129
138
|
end
|
|
130
139
|
|
|
131
140
|
class AlertRules < Namespace
|
|
@@ -139,6 +148,7 @@ module Watchfor
|
|
|
139
148
|
class Incidents < Namespace
|
|
140
149
|
def list(**query) = @c.request("GET", "/incidents", query: query)
|
|
141
150
|
def get(id) = @c.request("GET", "/incidents/#{id}")
|
|
151
|
+
# Every action returns the full, updated incident.
|
|
142
152
|
def acknowledge(id) = @c.request("POST", "/incidents/#{id}/acknowledge")
|
|
143
153
|
def resolve(id) = @c.request("POST", "/incidents/#{id}/resolve")
|
|
144
154
|
# Mark a RESOLVED incident as a false positive: kept on record, left out of
|
|
@@ -169,7 +179,48 @@ module Watchfor
|
|
|
169
179
|
def get(id) = @c.request("GET", "/contact-groups/#{id}")
|
|
170
180
|
def create(body, idempotency_key: nil) = @c.request("POST", "/contact-groups", body: body, idempotency_key: idempotency_key)
|
|
171
181
|
def update(id, body) = @c.request("PATCH", "/contact-groups/#{id}", body: body)
|
|
172
|
-
|
|
182
|
+
# 409 for the default group, and 409 while alert rules, monitors or notification channels
|
|
183
|
+
# still route through it; force: true deletes anyway (references are removed; affected
|
|
184
|
+
# alert rules fall back to their monitor's notification groups).
|
|
185
|
+
def delete(id, force: false) = @c.request("DELETE", "/contact-groups/#{id}", query: force ? { force: true } : nil)
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
# Live checks run from WatchFor's probe fleet against any public target,
|
|
189
|
+
# monitored or not — these MEASURE right now, unlike the rest of the
|
|
190
|
+
# client, which reads what WatchFor already recorded.
|
|
191
|
+
#
|
|
192
|
+
# +run+ and +diagnose_target+ need a write-scope key: a probe sends real
|
|
193
|
+
# traffic from WatchFor's IPs and spends a metered, per-plan allowance
|
|
194
|
+
# (the same hourly budget the dashboard Toolbox spends). Every response
|
|
195
|
+
# carries "remaining" and "distinct_targets" so a long investigation can
|
|
196
|
+
# pace itself. A check that ran and found a problem (closed port,
|
|
197
|
+
# NXDOMAIN, failed handshake) returns normally with "success" => false;
|
|
198
|
+
# only calls that could not run raise.
|
|
199
|
+
class Diagnostics < Namespace
|
|
200
|
+
# Every check, the question it answers, its options and this
|
|
201
|
+
# organization's remaining allowance. Read scope.
|
|
202
|
+
def list = @c.request("GET", "/diagnostics")
|
|
203
|
+
|
|
204
|
+
# Run one check (dns-lookup, ping, tls-grade, ...). +location_id+ is
|
|
205
|
+
# honoured on plans whose location_select is true; otherwise the
|
|
206
|
+
# nearest probe answers and the response says which. Write scope.
|
|
207
|
+
def run(slug, target, options: nil, location_id: nil, idempotency_key: nil)
|
|
208
|
+
body = { target: target }
|
|
209
|
+
body[:options] = options unless options.nil?
|
|
210
|
+
body[:location_id] = location_id unless location_id.nil?
|
|
211
|
+
@c.request("POST", "/diagnostics/#{slug}", body: body, idempotency_key: idempotency_key)
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
# DNS, propagation, TLS grade, HTTP headers and ping from up to 3
|
|
215
|
+
# regions in one call, aggregated into a single verdict. Each
|
|
216
|
+
# underlying check spends its own allowance; needs a plan with
|
|
217
|
+
# location selection. Write scope.
|
|
218
|
+
def diagnose_target(target, max_locations: nil, location_ids: nil, idempotency_key: nil)
|
|
219
|
+
body = { target: target }
|
|
220
|
+
body[:max_locations] = max_locations unless max_locations.nil?
|
|
221
|
+
body[:location_ids] = location_ids unless location_ids.nil?
|
|
222
|
+
@c.request("POST", "/diagnostics/diagnose-target", body: body, idempotency_key: idempotency_key)
|
|
223
|
+
end
|
|
173
224
|
end
|
|
174
225
|
end
|
|
175
226
|
end
|
data/lib/watchfor/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: watchfor
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.7.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- WatchFor
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-09-
|
|
11
|
+
date: 2026-09-07 00:00:00.000000000 Z
|
|
12
12
|
dependencies: []
|
|
13
13
|
description: Zero-dependency Ruby client for the WatchFor REST API (monitors, alert
|
|
14
14
|
rules, incidents, maintenance windows) plus a `watchfor` CLI.
|