@sonde/packs 0.1.2 → 0.1.4
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.
- package/.turbo/turbo-build.log +6 -4
- package/.turbo/turbo-test.log +1343 -53
- package/CHANGELOG.md +18 -0
- package/dist/docker/manifest (# Edit conflict 2026-02-19 LIl7ilN #).js +54 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -0
- package/dist/index.js.map +1 -1
- package/dist/integrations/a10.d.ts +3 -0
- package/dist/integrations/a10.d.ts.map +1 -0
- package/dist/integrations/a10.js +218 -0
- package/dist/integrations/a10.js.map +1 -0
- package/dist/integrations/checkpoint.d.ts +3 -0
- package/dist/integrations/checkpoint.d.ts.map +1 -0
- package/dist/integrations/checkpoint.js +249 -0
- package/dist/integrations/checkpoint.js.map +1 -0
- package/dist/integrations/datadog.d.ts +3 -0
- package/dist/integrations/datadog.d.ts.map +1 -0
- package/dist/integrations/datadog.js +195 -0
- package/dist/integrations/datadog.js.map +1 -0
- package/dist/integrations/jira.d.ts +3 -0
- package/dist/integrations/jira.d.ts.map +1 -0
- package/dist/integrations/jira.js +199 -0
- package/dist/integrations/jira.js.map +1 -0
- package/dist/integrations/loki.d.ts +3 -0
- package/dist/integrations/loki.d.ts.map +1 -0
- package/dist/integrations/loki.js +178 -0
- package/dist/integrations/loki.js.map +1 -0
- package/dist/integrations/meraki.d.ts +3 -0
- package/dist/integrations/meraki.d.ts.map +1 -0
- package/dist/integrations/meraki.js +238 -0
- package/dist/integrations/meraki.js.map +1 -0
- package/dist/integrations/pagerduty.d.ts +3 -0
- package/dist/integrations/pagerduty.d.ts.map +1 -0
- package/dist/integrations/pagerduty.js +229 -0
- package/dist/integrations/pagerduty.js.map +1 -0
- package/dist/integrations/thousandeyes.d.ts +3 -0
- package/dist/integrations/thousandeyes.d.ts.map +1 -0
- package/dist/integrations/thousandeyes.js +263 -0
- package/dist/integrations/thousandeyes.js.map +1 -0
- package/dist/integrations/vcenter.d.ts +3 -0
- package/dist/integrations/vcenter.d.ts.map +1 -0
- package/dist/integrations/vcenter.js +190 -0
- package/dist/integrations/vcenter.js.map +1 -0
- package/dist/proxmox/probes/ceph-status.test.d.ts (# Edit conflict 2026-02-19 N25hAvJ #).map +1 -0
- package/dist/system/index.d.ts.map +1 -1
- package/dist/system/index.js +8 -0
- package/dist/system/index.js.map +1 -1
- package/dist/system/manifest.d.ts.map +1 -1
- package/dist/system/manifest.js +82 -3
- package/dist/system/manifest.js.map +1 -1
- package/dist/system/probes/logs-dmesg.d.ts +13 -0
- package/dist/system/probes/logs-dmesg.d.ts.map +1 -0
- package/dist/system/probes/logs-dmesg.js +22 -0
- package/dist/system/probes/logs-dmesg.js.map +1 -0
- package/dist/system/probes/logs-dmesg.test.d.ts +2 -0
- package/dist/system/probes/logs-dmesg.test.d.ts.map +1 -0
- package/dist/system/probes/logs-dmesg.test.js +55 -0
- package/dist/system/probes/logs-dmesg.test.js.map +1 -0
- package/dist/system/probes/logs-journal.d.ts +21 -0
- package/dist/system/probes/logs-journal.d.ts.map +1 -0
- package/dist/system/probes/logs-journal.js +70 -0
- package/dist/system/probes/logs-journal.js.map +1 -0
- package/dist/system/probes/logs-journal.test.d.ts +2 -0
- package/dist/system/probes/logs-journal.test.d.ts.map +1 -0
- package/dist/system/probes/logs-journal.test.js +113 -0
- package/dist/system/probes/logs-journal.test.js.map +1 -0
- package/dist/system/probes/logs-tail.d.ts +14 -0
- package/dist/system/probes/logs-tail.d.ts.map +1 -0
- package/dist/system/probes/logs-tail.js +40 -0
- package/dist/system/probes/logs-tail.js.map +1 -0
- package/dist/system/probes/logs-tail.test.d.ts +2 -0
- package/dist/system/probes/logs-tail.test.d.ts.map +1 -0
- package/dist/system/probes/logs-tail.test.js +82 -0
- package/dist/system/probes/logs-tail.test.js.map +1 -0
- package/dist/system/probes/traceroute.d.ts +17 -0
- package/dist/system/probes/traceroute.d.ts.map +1 -0
- package/dist/system/probes/traceroute.js +72 -0
- package/dist/system/probes/traceroute.js.map +1 -0
- package/dist/system/probes/traceroute.test.d.ts +2 -0
- package/dist/system/probes/traceroute.test.d.ts.map +1 -0
- package/dist/system/probes/traceroute.test.js +98 -0
- package/dist/system/probes/traceroute.test.js.map +1 -0
- package/package.json +1 -1
- package/src/index.ts +9 -0
- package/src/integrations/a10.ts +370 -0
- package/src/integrations/checkpoint.ts +381 -0
- package/src/integrations/datadog.ts +281 -0
- package/src/integrations/jira.ts +272 -0
- package/src/integrations/loki.ts +228 -0
- package/src/integrations/meraki.ts +344 -0
- package/src/integrations/pagerduty.ts +319 -0
- package/src/integrations/thousandeyes.ts +353 -0
- package/src/integrations/vcenter.ts +261 -0
- package/src/system/index.ts +8 -0
- package/src/system/manifest.ts +93 -3
- package/src/system/probes/logs-dmesg.test.ts +83 -0
- package/src/system/probes/logs-dmesg.ts +38 -0
- package/src/system/probes/logs-journal.test.ts +142 -0
- package/src/system/probes/logs-journal.ts +103 -0
- package/src/system/probes/logs-tail.test.ts +140 -0
- package/src/system/probes/logs-tail.ts +70 -0
- package/src/system/probes/traceroute.test.ts +149 -0
- package/src/system/probes/traceroute.ts +99 -0
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -0,0 +1,381 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
FetchFn,
|
|
3
|
+
IntegrationConfig,
|
|
4
|
+
IntegrationCredentials,
|
|
5
|
+
IntegrationPack,
|
|
6
|
+
IntegrationProbeHandler,
|
|
7
|
+
} from '@sonde/shared';
|
|
8
|
+
|
|
9
|
+
// --- Session-based auth ---
|
|
10
|
+
|
|
11
|
+
/** Session ID cache: sid + expiry timestamp */
|
|
12
|
+
let cachedSession: { sid: string; expiresAt: number } | null = null;
|
|
13
|
+
|
|
14
|
+
/** Acquire a Check Point session ID via POST /web_api/login */
|
|
15
|
+
async function getSessionId(
|
|
16
|
+
config: IntegrationConfig,
|
|
17
|
+
credentials: IntegrationCredentials,
|
|
18
|
+
fetchFn: FetchFn,
|
|
19
|
+
): Promise<string> {
|
|
20
|
+
const now = Date.now();
|
|
21
|
+
if (cachedSession && cachedSession.expiresAt > now) {
|
|
22
|
+
return cachedSession.sid;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const { username, password } = credentials.credentials;
|
|
26
|
+
if (!username || !password) {
|
|
27
|
+
throw new Error('Check Point requires username and password credentials');
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const url = `${config.endpoint.replace(/\/$/, '')}/web_api/login`;
|
|
31
|
+
const res = await fetchFn(url, {
|
|
32
|
+
method: 'POST',
|
|
33
|
+
headers: {
|
|
34
|
+
'Content-Type': 'application/json',
|
|
35
|
+
...config.headers,
|
|
36
|
+
},
|
|
37
|
+
body: JSON.stringify({ user: username, password }),
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
if (!res.ok) {
|
|
41
|
+
throw new Error(`Check Point login failed: ${res.status} ${res.statusText}`);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const data = (await res.json()) as { sid?: string };
|
|
45
|
+
if (!data.sid) {
|
|
46
|
+
throw new Error('Check Point login response missing sid');
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
cachedSession = { sid: data.sid, expiresAt: now + 5 * 60 * 1000 };
|
|
50
|
+
return data.sid;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// --- REST helper ---
|
|
54
|
+
|
|
55
|
+
async function cpPost<T>(
|
|
56
|
+
command: string,
|
|
57
|
+
config: IntegrationConfig,
|
|
58
|
+
credentials: IntegrationCredentials,
|
|
59
|
+
fetchFn: FetchFn,
|
|
60
|
+
body?: Record<string, unknown>,
|
|
61
|
+
): Promise<T> {
|
|
62
|
+
const sid = await getSessionId(config, credentials, fetchFn);
|
|
63
|
+
const url = `${config.endpoint.replace(/\/$/, '')}/web_api/${command}`;
|
|
64
|
+
|
|
65
|
+
const headers: Record<string, string> = {
|
|
66
|
+
'Content-Type': 'application/json',
|
|
67
|
+
'X-chkp-sid': sid,
|
|
68
|
+
...config.headers,
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
const res = await fetchFn(url, {
|
|
72
|
+
method: 'POST',
|
|
73
|
+
headers,
|
|
74
|
+
body: JSON.stringify(body ?? {}),
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
if (!res.ok) {
|
|
78
|
+
throw new Error(`Check Point API returned ${res.status}: ${res.statusText}`);
|
|
79
|
+
}
|
|
80
|
+
return (await res.json()) as T;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// --- Probe handlers ---
|
|
84
|
+
|
|
85
|
+
const gatewaysList: IntegrationProbeHandler = async (
|
|
86
|
+
params,
|
|
87
|
+
config,
|
|
88
|
+
credentials,
|
|
89
|
+
fetchFn,
|
|
90
|
+
) => {
|
|
91
|
+
const detailsLevel = (params?.detailsLevel as string) || 'standard';
|
|
92
|
+
const data = await cpPost<{
|
|
93
|
+
objects?: Array<{
|
|
94
|
+
uid?: string;
|
|
95
|
+
name?: string;
|
|
96
|
+
type?: string;
|
|
97
|
+
'ipv4-address'?: string;
|
|
98
|
+
policy?: { name?: string };
|
|
99
|
+
version?: string;
|
|
100
|
+
}>;
|
|
101
|
+
total?: number;
|
|
102
|
+
}>('show-gateways-and-servers', config, credentials, fetchFn, {
|
|
103
|
+
'details-level': detailsLevel,
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
const gateways = (data.objects ?? []).map((gw) => ({
|
|
107
|
+
uid: gw.uid ?? null,
|
|
108
|
+
name: gw.name ?? null,
|
|
109
|
+
type: gw.type ?? null,
|
|
110
|
+
ipv4Address: gw['ipv4-address'] ?? null,
|
|
111
|
+
policy: gw.policy?.name ?? null,
|
|
112
|
+
version: gw.version ?? null,
|
|
113
|
+
}));
|
|
114
|
+
|
|
115
|
+
return { gateways, count: gateways.length, total: data.total ?? gateways.length };
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
const accessLayersList: IntegrationProbeHandler = async (
|
|
119
|
+
params,
|
|
120
|
+
config,
|
|
121
|
+
credentials,
|
|
122
|
+
fetchFn,
|
|
123
|
+
) => {
|
|
124
|
+
const body: Record<string, unknown> = {};
|
|
125
|
+
const limit = params?.limit as number | undefined;
|
|
126
|
+
if (limit) body.limit = limit;
|
|
127
|
+
|
|
128
|
+
const data = await cpPost<{
|
|
129
|
+
'access-layers'?: Array<{
|
|
130
|
+
uid?: string;
|
|
131
|
+
name?: string;
|
|
132
|
+
domain?: { name?: string };
|
|
133
|
+
}>;
|
|
134
|
+
total?: number;
|
|
135
|
+
}>('show-access-layers', config, credentials, fetchFn, body);
|
|
136
|
+
|
|
137
|
+
const layers = (data['access-layers'] ?? []).map((l) => ({
|
|
138
|
+
uid: l.uid ?? null,
|
|
139
|
+
name: l.name ?? null,
|
|
140
|
+
domain: l.domain?.name ?? null,
|
|
141
|
+
}));
|
|
142
|
+
|
|
143
|
+
return { layers, count: layers.length, total: data.total ?? layers.length };
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
const accessRulesShow: IntegrationProbeHandler = async (
|
|
147
|
+
params,
|
|
148
|
+
config,
|
|
149
|
+
credentials,
|
|
150
|
+
fetchFn,
|
|
151
|
+
) => {
|
|
152
|
+
const name = params?.name as string;
|
|
153
|
+
if (!name) throw new Error('name parameter is required (access layer name)');
|
|
154
|
+
|
|
155
|
+
const body: Record<string, unknown> = { name };
|
|
156
|
+
const limit = params?.limit as number | undefined;
|
|
157
|
+
if (limit) body.limit = limit;
|
|
158
|
+
|
|
159
|
+
const data = await cpPost<{
|
|
160
|
+
rulebase?: Array<{
|
|
161
|
+
'rule-number'?: number;
|
|
162
|
+
action?: { name?: string };
|
|
163
|
+
source?: Array<{ name?: string }>;
|
|
164
|
+
destination?: Array<{ name?: string }>;
|
|
165
|
+
service?: Array<{ name?: string }>;
|
|
166
|
+
enabled?: boolean;
|
|
167
|
+
}>;
|
|
168
|
+
total?: number;
|
|
169
|
+
}>('show-access-rulebase', config, credentials, fetchFn, body);
|
|
170
|
+
|
|
171
|
+
const rules = (data.rulebase ?? []).map((r) => ({
|
|
172
|
+
ruleNumber: r['rule-number'] ?? null,
|
|
173
|
+
action: r.action?.name ?? null,
|
|
174
|
+
source: (r.source ?? []).map((s) => s.name ?? 'unknown'),
|
|
175
|
+
destination: (r.destination ?? []).map((d) => d.name ?? 'unknown'),
|
|
176
|
+
service: (r.service ?? []).map((s) => s.name ?? 'unknown'),
|
|
177
|
+
enabled: r.enabled ?? true,
|
|
178
|
+
}));
|
|
179
|
+
|
|
180
|
+
return { rules, count: rules.length, total: data.total ?? rules.length };
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
const hostsList: IntegrationProbeHandler = async (
|
|
184
|
+
params,
|
|
185
|
+
config,
|
|
186
|
+
credentials,
|
|
187
|
+
fetchFn,
|
|
188
|
+
) => {
|
|
189
|
+
const body: Record<string, unknown> = {};
|
|
190
|
+
const limit = params?.limit as number | undefined;
|
|
191
|
+
if (limit) body.limit = limit;
|
|
192
|
+
|
|
193
|
+
const data = await cpPost<{
|
|
194
|
+
objects?: Array<{
|
|
195
|
+
uid?: string;
|
|
196
|
+
name?: string;
|
|
197
|
+
'ipv4-address'?: string;
|
|
198
|
+
}>;
|
|
199
|
+
total?: number;
|
|
200
|
+
}>('show-hosts', config, credentials, fetchFn, body);
|
|
201
|
+
|
|
202
|
+
const hosts = (data.objects ?? []).map((h) => ({
|
|
203
|
+
uid: h.uid ?? null,
|
|
204
|
+
name: h.name ?? null,
|
|
205
|
+
ipv4Address: h['ipv4-address'] ?? null,
|
|
206
|
+
}));
|
|
207
|
+
|
|
208
|
+
return { hosts, count: hosts.length, total: data.total ?? hosts.length };
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
const networksList: IntegrationProbeHandler = async (
|
|
212
|
+
params,
|
|
213
|
+
config,
|
|
214
|
+
credentials,
|
|
215
|
+
fetchFn,
|
|
216
|
+
) => {
|
|
217
|
+
const body: Record<string, unknown> = {};
|
|
218
|
+
const limit = params?.limit as number | undefined;
|
|
219
|
+
if (limit) body.limit = limit;
|
|
220
|
+
|
|
221
|
+
const data = await cpPost<{
|
|
222
|
+
objects?: Array<{
|
|
223
|
+
uid?: string;
|
|
224
|
+
name?: string;
|
|
225
|
+
subnet4?: string;
|
|
226
|
+
'mask-length4'?: number;
|
|
227
|
+
}>;
|
|
228
|
+
total?: number;
|
|
229
|
+
}>('show-networks', config, credentials, fetchFn, body);
|
|
230
|
+
|
|
231
|
+
const networks = (data.objects ?? []).map((n) => ({
|
|
232
|
+
uid: n.uid ?? null,
|
|
233
|
+
name: n.name ?? null,
|
|
234
|
+
subnet4: n.subnet4 ?? null,
|
|
235
|
+
maskLength4: n['mask-length4'] ?? null,
|
|
236
|
+
}));
|
|
237
|
+
|
|
238
|
+
return { networks, count: networks.length, total: data.total ?? networks.length };
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
const tasksList: IntegrationProbeHandler = async (
|
|
242
|
+
_params,
|
|
243
|
+
config,
|
|
244
|
+
credentials,
|
|
245
|
+
fetchFn,
|
|
246
|
+
) => {
|
|
247
|
+
const data = await cpPost<{
|
|
248
|
+
tasks?: Array<{
|
|
249
|
+
'task-id'?: string;
|
|
250
|
+
'task-name'?: string;
|
|
251
|
+
status?: string;
|
|
252
|
+
progress?: number;
|
|
253
|
+
'start-date'?: string;
|
|
254
|
+
}>;
|
|
255
|
+
}>('show-tasks', config, credentials, fetchFn);
|
|
256
|
+
|
|
257
|
+
const tasks = (data.tasks ?? []).map((t) => ({
|
|
258
|
+
taskId: t['task-id'] ?? null,
|
|
259
|
+
taskName: t['task-name'] ?? null,
|
|
260
|
+
status: t.status ?? null,
|
|
261
|
+
progress: t.progress ?? null,
|
|
262
|
+
startDate: t['start-date'] ?? null,
|
|
263
|
+
}));
|
|
264
|
+
|
|
265
|
+
return { tasks, count: tasks.length };
|
|
266
|
+
};
|
|
267
|
+
|
|
268
|
+
// --- Pack definition ---
|
|
269
|
+
|
|
270
|
+
export const checkpointPack: IntegrationPack = {
|
|
271
|
+
manifest: {
|
|
272
|
+
name: 'checkpoint',
|
|
273
|
+
type: 'integration',
|
|
274
|
+
version: '0.1.0',
|
|
275
|
+
description:
|
|
276
|
+
'Check Point — firewall gateways, access policies, network objects, and management tasks',
|
|
277
|
+
requires: { groups: [], files: [], commands: [] },
|
|
278
|
+
probes: [
|
|
279
|
+
{
|
|
280
|
+
name: 'gateways.list',
|
|
281
|
+
description: 'List gateways and servers with policy and version info',
|
|
282
|
+
capability: 'observe',
|
|
283
|
+
params: {
|
|
284
|
+
detailsLevel: {
|
|
285
|
+
type: 'string',
|
|
286
|
+
description: 'Detail level: uid, standard, or full (default: standard)',
|
|
287
|
+
required: false,
|
|
288
|
+
},
|
|
289
|
+
},
|
|
290
|
+
timeout: 15000,
|
|
291
|
+
},
|
|
292
|
+
{
|
|
293
|
+
name: 'access-layers.list',
|
|
294
|
+
description: 'List access control policy layers',
|
|
295
|
+
capability: 'observe',
|
|
296
|
+
params: {
|
|
297
|
+
limit: {
|
|
298
|
+
type: 'number',
|
|
299
|
+
description: 'Max results to return',
|
|
300
|
+
required: false,
|
|
301
|
+
},
|
|
302
|
+
},
|
|
303
|
+
timeout: 15000,
|
|
304
|
+
},
|
|
305
|
+
{
|
|
306
|
+
name: 'access-rules.show',
|
|
307
|
+
description: 'Show access rulebase for a named layer',
|
|
308
|
+
capability: 'observe',
|
|
309
|
+
params: {
|
|
310
|
+
name: {
|
|
311
|
+
type: 'string',
|
|
312
|
+
description: 'Access layer name',
|
|
313
|
+
required: true,
|
|
314
|
+
},
|
|
315
|
+
limit: {
|
|
316
|
+
type: 'number',
|
|
317
|
+
description: 'Max rules to return',
|
|
318
|
+
required: false,
|
|
319
|
+
},
|
|
320
|
+
},
|
|
321
|
+
timeout: 15000,
|
|
322
|
+
},
|
|
323
|
+
{
|
|
324
|
+
name: 'hosts.list',
|
|
325
|
+
description: 'List host objects with IP addresses',
|
|
326
|
+
capability: 'observe',
|
|
327
|
+
params: {
|
|
328
|
+
limit: {
|
|
329
|
+
type: 'number',
|
|
330
|
+
description: 'Max results to return',
|
|
331
|
+
required: false,
|
|
332
|
+
},
|
|
333
|
+
},
|
|
334
|
+
timeout: 15000,
|
|
335
|
+
},
|
|
336
|
+
{
|
|
337
|
+
name: 'networks.list',
|
|
338
|
+
description: 'List network objects with subnet and mask',
|
|
339
|
+
capability: 'observe',
|
|
340
|
+
params: {
|
|
341
|
+
limit: {
|
|
342
|
+
type: 'number',
|
|
343
|
+
description: 'Max results to return',
|
|
344
|
+
required: false,
|
|
345
|
+
},
|
|
346
|
+
},
|
|
347
|
+
timeout: 15000,
|
|
348
|
+
},
|
|
349
|
+
{
|
|
350
|
+
name: 'tasks.list',
|
|
351
|
+
description: 'List recent management tasks with status and progress',
|
|
352
|
+
capability: 'observe',
|
|
353
|
+
params: {},
|
|
354
|
+
timeout: 15000,
|
|
355
|
+
},
|
|
356
|
+
],
|
|
357
|
+
runbook: {
|
|
358
|
+
category: 'security',
|
|
359
|
+
probes: ['gateways.list', 'tasks.list'],
|
|
360
|
+
parallel: true,
|
|
361
|
+
},
|
|
362
|
+
},
|
|
363
|
+
|
|
364
|
+
handlers: {
|
|
365
|
+
'gateways.list': gatewaysList,
|
|
366
|
+
'access-layers.list': accessLayersList,
|
|
367
|
+
'access-rules.show': accessRulesShow,
|
|
368
|
+
'hosts.list': hostsList,
|
|
369
|
+
'networks.list': networksList,
|
|
370
|
+
'tasks.list': tasksList,
|
|
371
|
+
},
|
|
372
|
+
|
|
373
|
+
testConnection: async (config, credentials, fetchFn) => {
|
|
374
|
+
try {
|
|
375
|
+
await getSessionId(config, credentials, fetchFn);
|
|
376
|
+
return true;
|
|
377
|
+
} catch {
|
|
378
|
+
return false;
|
|
379
|
+
}
|
|
380
|
+
},
|
|
381
|
+
};
|
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
FetchFn,
|
|
3
|
+
IntegrationConfig,
|
|
4
|
+
IntegrationCredentials,
|
|
5
|
+
IntegrationPack,
|
|
6
|
+
IntegrationProbeHandler,
|
|
7
|
+
} from '@sonde/shared';
|
|
8
|
+
|
|
9
|
+
// --- Auth helpers ---
|
|
10
|
+
|
|
11
|
+
/** Build Datadog auth headers: DD-API-KEY + DD-APPLICATION-KEY */
|
|
12
|
+
function buildAuthHeaders(credentials: IntegrationCredentials): Record<string, string> {
|
|
13
|
+
const apiKey = credentials.credentials.apiKey ?? '';
|
|
14
|
+
const appKey = credentials.credentials.appKey ?? '';
|
|
15
|
+
return {
|
|
16
|
+
'DD-API-KEY': apiKey,
|
|
17
|
+
'DD-APPLICATION-KEY': appKey,
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// --- REST helper ---
|
|
22
|
+
|
|
23
|
+
async function datadogGet<T>(
|
|
24
|
+
path: string,
|
|
25
|
+
config: IntegrationConfig,
|
|
26
|
+
credentials: IntegrationCredentials,
|
|
27
|
+
fetchFn: FetchFn,
|
|
28
|
+
params?: Record<string, string>,
|
|
29
|
+
): Promise<T> {
|
|
30
|
+
const base = `${config.endpoint.replace(/\/$/, '')}${path}`;
|
|
31
|
+
const url = new URL(base);
|
|
32
|
+
if (params) {
|
|
33
|
+
for (const [key, value] of Object.entries(params)) {
|
|
34
|
+
url.searchParams.set(key, value);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const headers: Record<string, string> = {
|
|
39
|
+
Accept: 'application/json',
|
|
40
|
+
...buildAuthHeaders(credentials),
|
|
41
|
+
...config.headers,
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const res = await fetchFn(url.toString(), { headers });
|
|
45
|
+
if (!res.ok) {
|
|
46
|
+
throw new Error(`Datadog API returned ${res.status}: ${res.statusText}`);
|
|
47
|
+
}
|
|
48
|
+
return (await res.json()) as T;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// --- Probe handlers ---
|
|
52
|
+
|
|
53
|
+
const monitorsList: IntegrationProbeHandler = async (params, config, credentials, fetchFn) => {
|
|
54
|
+
const queryParams: Record<string, string> = {};
|
|
55
|
+
const tags = params?.tags as string | undefined;
|
|
56
|
+
if (tags) queryParams.tags = tags;
|
|
57
|
+
|
|
58
|
+
const monitors = await datadogGet<
|
|
59
|
+
Array<{
|
|
60
|
+
id: number;
|
|
61
|
+
name: string;
|
|
62
|
+
type: string;
|
|
63
|
+
overall_state: string;
|
|
64
|
+
tags?: string[];
|
|
65
|
+
message?: string;
|
|
66
|
+
}>
|
|
67
|
+
>('/api/v1/monitor', config, credentials, fetchFn, queryParams);
|
|
68
|
+
|
|
69
|
+
return {
|
|
70
|
+
monitors: monitors.map((m) => ({
|
|
71
|
+
id: m.id,
|
|
72
|
+
name: m.name,
|
|
73
|
+
type: m.type,
|
|
74
|
+
overallState: m.overall_state,
|
|
75
|
+
tags: m.tags ?? [],
|
|
76
|
+
})),
|
|
77
|
+
count: monitors.length,
|
|
78
|
+
};
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
const monitorsTriggered: IntegrationProbeHandler = async (
|
|
82
|
+
_params,
|
|
83
|
+
config,
|
|
84
|
+
credentials,
|
|
85
|
+
fetchFn,
|
|
86
|
+
) => {
|
|
87
|
+
const monitors = await datadogGet<
|
|
88
|
+
Array<{
|
|
89
|
+
id: number;
|
|
90
|
+
name: string;
|
|
91
|
+
type: string;
|
|
92
|
+
overall_state: string;
|
|
93
|
+
tags?: string[];
|
|
94
|
+
}>
|
|
95
|
+
>('/api/v1/monitor', config, credentials, fetchFn);
|
|
96
|
+
|
|
97
|
+
const triggered = monitors.filter(
|
|
98
|
+
(m) => m.overall_state === 'Alert' ||
|
|
99
|
+
m.overall_state === 'Warn' ||
|
|
100
|
+
m.overall_state === 'No Data',
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
return {
|
|
104
|
+
monitors: triggered.map((m) => ({
|
|
105
|
+
id: m.id,
|
|
106
|
+
name: m.name,
|
|
107
|
+
type: m.type,
|
|
108
|
+
overallState: m.overall_state,
|
|
109
|
+
tags: m.tags ?? [],
|
|
110
|
+
})),
|
|
111
|
+
count: triggered.length,
|
|
112
|
+
};
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
const hostsList: IntegrationProbeHandler = async (_params, config, credentials, fetchFn) => {
|
|
116
|
+
const data = await datadogGet<{
|
|
117
|
+
host_list?: Array<{
|
|
118
|
+
name: string;
|
|
119
|
+
id: number;
|
|
120
|
+
up?: boolean;
|
|
121
|
+
is_muted?: boolean;
|
|
122
|
+
apps?: string[];
|
|
123
|
+
meta?: { platform?: string };
|
|
124
|
+
}>;
|
|
125
|
+
total_matching?: number;
|
|
126
|
+
}>('/api/v1/hosts', config, credentials, fetchFn);
|
|
127
|
+
|
|
128
|
+
const hosts = data.host_list ?? [];
|
|
129
|
+
return {
|
|
130
|
+
hosts: hosts.map((h) => ({
|
|
131
|
+
name: h.name,
|
|
132
|
+
id: h.id,
|
|
133
|
+
up: h.up ?? false,
|
|
134
|
+
isMuted: h.is_muted ?? false,
|
|
135
|
+
apps: h.apps ?? [],
|
|
136
|
+
platform: h.meta?.platform ?? null,
|
|
137
|
+
})),
|
|
138
|
+
count: data.total_matching ?? hosts.length,
|
|
139
|
+
};
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
const eventsList: IntegrationProbeHandler = async (params, config, credentials, fetchFn) => {
|
|
143
|
+
const hours = (params?.hours as number) || 24;
|
|
144
|
+
const now = Math.floor(Date.now() / 1000);
|
|
145
|
+
const start = now - hours * 3600;
|
|
146
|
+
|
|
147
|
+
const queryParams: Record<string, string> = {
|
|
148
|
+
'filter[from]': String(start),
|
|
149
|
+
'filter[to]': String(now),
|
|
150
|
+
};
|
|
151
|
+
const query = params?.query as string | undefined;
|
|
152
|
+
if (query) queryParams['filter[query]'] = query;
|
|
153
|
+
|
|
154
|
+
const data = await datadogGet<{
|
|
155
|
+
data?: Array<{
|
|
156
|
+
id?: string;
|
|
157
|
+
attributes?: {
|
|
158
|
+
title?: string;
|
|
159
|
+
message?: string;
|
|
160
|
+
timestamp?: string;
|
|
161
|
+
tags?: string[];
|
|
162
|
+
status?: string;
|
|
163
|
+
};
|
|
164
|
+
}>;
|
|
165
|
+
}>('/api/v2/events', config, credentials, fetchFn, queryParams);
|
|
166
|
+
|
|
167
|
+
const events = data.data ?? [];
|
|
168
|
+
return {
|
|
169
|
+
events: events.map((e) => ({
|
|
170
|
+
id: e.id ?? null,
|
|
171
|
+
title: e.attributes?.title ?? null,
|
|
172
|
+
message: e.attributes?.message ?? null,
|
|
173
|
+
timestamp: e.attributes?.timestamp ?? null,
|
|
174
|
+
tags: e.attributes?.tags ?? [],
|
|
175
|
+
status: e.attributes?.status ?? null,
|
|
176
|
+
})),
|
|
177
|
+
count: events.length,
|
|
178
|
+
};
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
const datadogHealth: IntegrationProbeHandler = async (_params, config, credentials, fetchFn) => {
|
|
182
|
+
const data = await datadogGet<{ valid?: boolean }>(
|
|
183
|
+
'/api/v1/validate',
|
|
184
|
+
config,
|
|
185
|
+
credentials,
|
|
186
|
+
fetchFn,
|
|
187
|
+
);
|
|
188
|
+
|
|
189
|
+
return { valid: data.valid ?? false };
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
// --- Pack definition ---
|
|
193
|
+
|
|
194
|
+
export const datadogPack: IntegrationPack = {
|
|
195
|
+
manifest: {
|
|
196
|
+
name: 'datadog',
|
|
197
|
+
type: 'integration',
|
|
198
|
+
version: '0.1.0',
|
|
199
|
+
description: 'Datadog — monitors, hosts, events, and API health',
|
|
200
|
+
requires: { groups: [], files: [], commands: [] },
|
|
201
|
+
probes: [
|
|
202
|
+
{
|
|
203
|
+
name: 'monitors.list',
|
|
204
|
+
description: 'List monitors with status and tags',
|
|
205
|
+
capability: 'observe',
|
|
206
|
+
params: {
|
|
207
|
+
tags: {
|
|
208
|
+
type: 'string',
|
|
209
|
+
description: 'Comma-separated tags to filter monitors',
|
|
210
|
+
required: false,
|
|
211
|
+
},
|
|
212
|
+
},
|
|
213
|
+
timeout: 15000,
|
|
214
|
+
},
|
|
215
|
+
{
|
|
216
|
+
name: 'monitors.triggered',
|
|
217
|
+
description: 'List monitors in Alert, Warn, or No Data state',
|
|
218
|
+
capability: 'observe',
|
|
219
|
+
params: {},
|
|
220
|
+
timeout: 15000,
|
|
221
|
+
},
|
|
222
|
+
{
|
|
223
|
+
name: 'hosts.list',
|
|
224
|
+
description: 'List infrastructure hosts with status',
|
|
225
|
+
capability: 'observe',
|
|
226
|
+
params: {},
|
|
227
|
+
timeout: 15000,
|
|
228
|
+
},
|
|
229
|
+
{
|
|
230
|
+
name: 'events.list',
|
|
231
|
+
description: 'List recent events with optional query filter',
|
|
232
|
+
capability: 'observe',
|
|
233
|
+
params: {
|
|
234
|
+
query: {
|
|
235
|
+
type: 'string',
|
|
236
|
+
description: 'Event search query',
|
|
237
|
+
required: false,
|
|
238
|
+
},
|
|
239
|
+
hours: {
|
|
240
|
+
type: 'number',
|
|
241
|
+
description: 'Hours to look back (default: 24)',
|
|
242
|
+
required: false,
|
|
243
|
+
},
|
|
244
|
+
},
|
|
245
|
+
timeout: 15000,
|
|
246
|
+
},
|
|
247
|
+
{
|
|
248
|
+
name: 'health',
|
|
249
|
+
description: 'Validate API key connectivity',
|
|
250
|
+
capability: 'observe',
|
|
251
|
+
params: {},
|
|
252
|
+
timeout: 10000,
|
|
253
|
+
},
|
|
254
|
+
],
|
|
255
|
+
runbook: {
|
|
256
|
+
category: 'observability',
|
|
257
|
+
probes: ['health', 'monitors.triggered'],
|
|
258
|
+
parallel: true,
|
|
259
|
+
},
|
|
260
|
+
},
|
|
261
|
+
|
|
262
|
+
handlers: {
|
|
263
|
+
'monitors.list': monitorsList,
|
|
264
|
+
'monitors.triggered': monitorsTriggered,
|
|
265
|
+
'hosts.list': hostsList,
|
|
266
|
+
'events.list': eventsList,
|
|
267
|
+
health: datadogHealth,
|
|
268
|
+
},
|
|
269
|
+
|
|
270
|
+
testConnection: async (config, credentials, fetchFn) => {
|
|
271
|
+
const url = `${config.endpoint.replace(/\/$/, '')}/api/v1/validate`;
|
|
272
|
+
const headers: Record<string, string> = {
|
|
273
|
+
Accept: 'application/json',
|
|
274
|
+
...buildAuthHeaders(credentials),
|
|
275
|
+
...config.headers,
|
|
276
|
+
};
|
|
277
|
+
|
|
278
|
+
const res = await fetchFn(url, { headers });
|
|
279
|
+
return res.ok;
|
|
280
|
+
},
|
|
281
|
+
};
|