@sonde/packs 0.1.2 → 0.1.3
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 +1 -1
- package/.turbo/turbo-test.log +39 -35
- package/CHANGELOG.md +9 -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,353 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
FetchFn,
|
|
3
|
+
IntegrationConfig,
|
|
4
|
+
IntegrationCredentials,
|
|
5
|
+
IntegrationPack,
|
|
6
|
+
IntegrationProbeHandler,
|
|
7
|
+
} from '@sonde/shared';
|
|
8
|
+
|
|
9
|
+
// --- Auth helpers ---
|
|
10
|
+
|
|
11
|
+
function buildAuthHeaders(credentials: IntegrationCredentials): Record<string, string> {
|
|
12
|
+
const token = credentials.credentials.token ?? '';
|
|
13
|
+
return { Authorization: `Bearer ${token}` };
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// --- REST helper ---
|
|
17
|
+
|
|
18
|
+
async function teGet<T>(
|
|
19
|
+
path: string,
|
|
20
|
+
config: IntegrationConfig,
|
|
21
|
+
credentials: IntegrationCredentials,
|
|
22
|
+
fetchFn: FetchFn,
|
|
23
|
+
params?: Record<string, string>,
|
|
24
|
+
): Promise<T> {
|
|
25
|
+
const base = `${config.endpoint.replace(/\/$/, '')}/v7${path}`;
|
|
26
|
+
const url = new URL(base);
|
|
27
|
+
if (params) {
|
|
28
|
+
for (const [key, value] of Object.entries(params)) {
|
|
29
|
+
url.searchParams.set(key, value);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const headers: Record<string, string> = {
|
|
34
|
+
Accept: 'application/json',
|
|
35
|
+
...buildAuthHeaders(credentials),
|
|
36
|
+
...config.headers,
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const res = await fetchFn(url.toString(), { headers });
|
|
40
|
+
if (!res.ok) {
|
|
41
|
+
throw new Error(`ThousandEyes API returned ${res.status}: ${res.statusText}`);
|
|
42
|
+
}
|
|
43
|
+
return (await res.json()) as T;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// --- Probe handlers ---
|
|
47
|
+
|
|
48
|
+
const alertsActive: IntegrationProbeHandler = async (params, config, credentials, fetchFn) => {
|
|
49
|
+
const queryParams: Record<string, string> = {};
|
|
50
|
+
const window = params?.window as string | undefined;
|
|
51
|
+
if (window) queryParams.window = window;
|
|
52
|
+
|
|
53
|
+
const data = await teGet<{
|
|
54
|
+
alerts?: Array<{
|
|
55
|
+
id?: string;
|
|
56
|
+
type?: string;
|
|
57
|
+
severity?: string;
|
|
58
|
+
state?: string;
|
|
59
|
+
startDate?: string;
|
|
60
|
+
violationCount?: number;
|
|
61
|
+
duration?: number;
|
|
62
|
+
}>;
|
|
63
|
+
}>('/alerts', config, credentials, fetchFn, queryParams);
|
|
64
|
+
|
|
65
|
+
const alerts = data.alerts ?? [];
|
|
66
|
+
return {
|
|
67
|
+
alerts: alerts.map((a) => ({
|
|
68
|
+
id: a.id ?? null,
|
|
69
|
+
type: a.type ?? null,
|
|
70
|
+
severity: a.severity ?? null,
|
|
71
|
+
state: a.state ?? null,
|
|
72
|
+
startDate: a.startDate ?? null,
|
|
73
|
+
violationCount: a.violationCount ?? 0,
|
|
74
|
+
duration: a.duration ?? 0,
|
|
75
|
+
})),
|
|
76
|
+
count: alerts.length,
|
|
77
|
+
};
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
const testsList: IntegrationProbeHandler = async (params, config, credentials, fetchFn) => {
|
|
81
|
+
const queryParams: Record<string, string> = {};
|
|
82
|
+
const type = params?.type as string | undefined;
|
|
83
|
+
if (type) queryParams.type = type;
|
|
84
|
+
|
|
85
|
+
const data = await teGet<{
|
|
86
|
+
tests?: Array<{
|
|
87
|
+
testId?: string;
|
|
88
|
+
testName?: string;
|
|
89
|
+
type?: string;
|
|
90
|
+
enabled?: boolean;
|
|
91
|
+
server?: string;
|
|
92
|
+
interval?: number;
|
|
93
|
+
agents?: Array<{ agentId?: string; agentName?: string }>;
|
|
94
|
+
}>;
|
|
95
|
+
}>('/tests', config, credentials, fetchFn, queryParams);
|
|
96
|
+
|
|
97
|
+
const tests = data.tests ?? [];
|
|
98
|
+
return {
|
|
99
|
+
tests: tests.map((t) => ({
|
|
100
|
+
testId: t.testId ?? null,
|
|
101
|
+
testName: t.testName ?? null,
|
|
102
|
+
type: t.type ?? null,
|
|
103
|
+
enabled: t.enabled ?? false,
|
|
104
|
+
server: t.server ?? null,
|
|
105
|
+
interval: t.interval ?? 0,
|
|
106
|
+
agents: t.agents ?? [],
|
|
107
|
+
})),
|
|
108
|
+
count: tests.length,
|
|
109
|
+
};
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
const networkMetrics: IntegrationProbeHandler = async (params, config, credentials, fetchFn) => {
|
|
113
|
+
const testId = params?.testId as string;
|
|
114
|
+
if (!testId) throw new Error('testId parameter is required');
|
|
115
|
+
|
|
116
|
+
const queryParams: Record<string, string> = {};
|
|
117
|
+
const window = params?.window as string | undefined;
|
|
118
|
+
if (window) queryParams.window = window;
|
|
119
|
+
|
|
120
|
+
const data = await teGet<{
|
|
121
|
+
results?: Array<{
|
|
122
|
+
agent?: { agentId?: string; agentName?: string; location?: string };
|
|
123
|
+
avgLatency?: number;
|
|
124
|
+
loss?: number;
|
|
125
|
+
jitter?: number;
|
|
126
|
+
serverIp?: string;
|
|
127
|
+
}>;
|
|
128
|
+
}>(`/test-results/${encodeURIComponent(testId)}/network`, config, credentials, fetchFn, queryParams);
|
|
129
|
+
|
|
130
|
+
const results = data.results ?? [];
|
|
131
|
+
return {
|
|
132
|
+
metrics: results.map((r) => ({
|
|
133
|
+
agentId: r.agent?.agentId ?? null,
|
|
134
|
+
agentName: r.agent?.agentName ?? null,
|
|
135
|
+
location: r.agent?.location ?? null,
|
|
136
|
+
avgLatency: r.avgLatency ?? null,
|
|
137
|
+
loss: r.loss ?? null,
|
|
138
|
+
jitter: r.jitter ?? null,
|
|
139
|
+
serverIp: r.serverIp ?? null,
|
|
140
|
+
})),
|
|
141
|
+
count: results.length,
|
|
142
|
+
};
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
const networkPathVis: IntegrationProbeHandler = async (params, config, credentials, fetchFn) => {
|
|
146
|
+
const testId = params?.testId as string;
|
|
147
|
+
if (!testId) throw new Error('testId parameter is required');
|
|
148
|
+
|
|
149
|
+
const queryParams: Record<string, string> = {};
|
|
150
|
+
const window = params?.window as string | undefined;
|
|
151
|
+
if (window) queryParams.window = window;
|
|
152
|
+
|
|
153
|
+
const data = await teGet<{
|
|
154
|
+
results?: Array<{
|
|
155
|
+
agent?: { agentId?: string; agentName?: string };
|
|
156
|
+
pathTraces?: Array<{
|
|
157
|
+
ipAddress?: string;
|
|
158
|
+
responseTime?: number;
|
|
159
|
+
numberOfHops?: number;
|
|
160
|
+
}>;
|
|
161
|
+
}>;
|
|
162
|
+
}>(`/test-results/${encodeURIComponent(testId)}/path-vis`, config, credentials, fetchFn, queryParams);
|
|
163
|
+
|
|
164
|
+
const results = data.results ?? [];
|
|
165
|
+
return {
|
|
166
|
+
pathVisualizations: results.map((r) => ({
|
|
167
|
+
agentId: r.agent?.agentId ?? null,
|
|
168
|
+
agentName: r.agent?.agentName ?? null,
|
|
169
|
+
pathTraces: (r.pathTraces ?? []).map((hop) => ({
|
|
170
|
+
ipAddress: hop.ipAddress ?? null,
|
|
171
|
+
responseTime: hop.responseTime ?? null,
|
|
172
|
+
numberOfHops: hop.numberOfHops ?? null,
|
|
173
|
+
})),
|
|
174
|
+
})),
|
|
175
|
+
count: results.length,
|
|
176
|
+
};
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
const agentsList: IntegrationProbeHandler = async (_params, config, credentials, fetchFn) => {
|
|
180
|
+
const data = await teGet<{
|
|
181
|
+
agents?: Array<{
|
|
182
|
+
agentId?: string;
|
|
183
|
+
agentName?: string;
|
|
184
|
+
agentType?: string;
|
|
185
|
+
location?: string;
|
|
186
|
+
countryId?: string;
|
|
187
|
+
enabled?: boolean;
|
|
188
|
+
}>;
|
|
189
|
+
}>('/agents', config, credentials, fetchFn);
|
|
190
|
+
|
|
191
|
+
const agents = data.agents ?? [];
|
|
192
|
+
return {
|
|
193
|
+
agents: agents.map((a) => ({
|
|
194
|
+
agentId: a.agentId ?? null,
|
|
195
|
+
agentName: a.agentName ?? null,
|
|
196
|
+
agentType: a.agentType ?? null,
|
|
197
|
+
location: a.location ?? null,
|
|
198
|
+
country: a.countryId ?? null,
|
|
199
|
+
enabled: a.enabled ?? false,
|
|
200
|
+
})),
|
|
201
|
+
count: agents.length,
|
|
202
|
+
};
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
const outagesNetwork: IntegrationProbeHandler = async (params, config, credentials, fetchFn) => {
|
|
206
|
+
const queryParams: Record<string, string> = {};
|
|
207
|
+
const window = params?.window as string | undefined;
|
|
208
|
+
if (window) queryParams.window = window;
|
|
209
|
+
|
|
210
|
+
const data = await teGet<{
|
|
211
|
+
outages?: Array<{
|
|
212
|
+
type?: string;
|
|
213
|
+
scope?: string;
|
|
214
|
+
startDate?: string;
|
|
215
|
+
endDate?: string;
|
|
216
|
+
affectedTests?: number[];
|
|
217
|
+
}>;
|
|
218
|
+
}>('/internet-insights/outages/network', config, credentials, fetchFn, queryParams);
|
|
219
|
+
|
|
220
|
+
const outages = data.outages ?? [];
|
|
221
|
+
return {
|
|
222
|
+
outages: outages.map((o) => ({
|
|
223
|
+
type: o.type ?? null,
|
|
224
|
+
scope: o.scope ?? null,
|
|
225
|
+
startDate: o.startDate ?? null,
|
|
226
|
+
endDate: o.endDate ?? null,
|
|
227
|
+
affectedTests: o.affectedTests ?? [],
|
|
228
|
+
})),
|
|
229
|
+
count: outages.length,
|
|
230
|
+
};
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
// --- Pack definition ---
|
|
234
|
+
|
|
235
|
+
export const thousandeyesPack: IntegrationPack = {
|
|
236
|
+
manifest: {
|
|
237
|
+
name: 'thousandeyes',
|
|
238
|
+
type: 'integration',
|
|
239
|
+
version: '0.1.0',
|
|
240
|
+
description: 'ThousandEyes — network path analysis, latency metrics, and outage detection',
|
|
241
|
+
requires: { groups: [], files: [], commands: [] },
|
|
242
|
+
probes: [
|
|
243
|
+
{
|
|
244
|
+
name: 'alerts.active',
|
|
245
|
+
description: 'List active alerts with severity and violation details',
|
|
246
|
+
capability: 'observe',
|
|
247
|
+
params: {
|
|
248
|
+
window: {
|
|
249
|
+
type: 'string',
|
|
250
|
+
description: 'Time window for alerts (e.g. "12h")',
|
|
251
|
+
required: false,
|
|
252
|
+
},
|
|
253
|
+
},
|
|
254
|
+
timeout: 15000,
|
|
255
|
+
},
|
|
256
|
+
{
|
|
257
|
+
name: 'tests.list',
|
|
258
|
+
description: 'List configured tests with type, interval, and assigned agents',
|
|
259
|
+
capability: 'observe',
|
|
260
|
+
params: {
|
|
261
|
+
type: {
|
|
262
|
+
type: 'string',
|
|
263
|
+
description: 'Filter by test type (e.g. "agent-to-server")',
|
|
264
|
+
required: false,
|
|
265
|
+
},
|
|
266
|
+
},
|
|
267
|
+
timeout: 15000,
|
|
268
|
+
},
|
|
269
|
+
{
|
|
270
|
+
name: 'network.metrics',
|
|
271
|
+
description: 'Get per-agent network metrics (latency, loss, jitter) for a test',
|
|
272
|
+
capability: 'observe',
|
|
273
|
+
params: {
|
|
274
|
+
testId: {
|
|
275
|
+
type: 'string',
|
|
276
|
+
description: 'ThousandEyes test ID',
|
|
277
|
+
required: true,
|
|
278
|
+
},
|
|
279
|
+
window: {
|
|
280
|
+
type: 'string',
|
|
281
|
+
description: 'Time window for results (e.g. "1h")',
|
|
282
|
+
required: false,
|
|
283
|
+
},
|
|
284
|
+
},
|
|
285
|
+
timeout: 15000,
|
|
286
|
+
},
|
|
287
|
+
{
|
|
288
|
+
name: 'network.path-vis',
|
|
289
|
+
description: 'Get hop-by-hop path visualization for a test',
|
|
290
|
+
capability: 'observe',
|
|
291
|
+
params: {
|
|
292
|
+
testId: {
|
|
293
|
+
type: 'string',
|
|
294
|
+
description: 'ThousandEyes test ID',
|
|
295
|
+
required: true,
|
|
296
|
+
},
|
|
297
|
+
window: {
|
|
298
|
+
type: 'string',
|
|
299
|
+
description: 'Time window for results (e.g. "1h")',
|
|
300
|
+
required: false,
|
|
301
|
+
},
|
|
302
|
+
},
|
|
303
|
+
timeout: 15000,
|
|
304
|
+
},
|
|
305
|
+
{
|
|
306
|
+
name: 'agents.list',
|
|
307
|
+
description: 'List ThousandEyes agents with type, location, and status',
|
|
308
|
+
capability: 'observe',
|
|
309
|
+
params: {},
|
|
310
|
+
timeout: 15000,
|
|
311
|
+
},
|
|
312
|
+
{
|
|
313
|
+
name: 'outages.network',
|
|
314
|
+
description: 'List detected internet outages affecting tests',
|
|
315
|
+
capability: 'observe',
|
|
316
|
+
params: {
|
|
317
|
+
window: {
|
|
318
|
+
type: 'string',
|
|
319
|
+
description: 'Time window for outages (e.g. "24h")',
|
|
320
|
+
required: false,
|
|
321
|
+
},
|
|
322
|
+
},
|
|
323
|
+
timeout: 15000,
|
|
324
|
+
},
|
|
325
|
+
],
|
|
326
|
+
runbook: {
|
|
327
|
+
category: 'network',
|
|
328
|
+
probes: ['alerts.active', 'agents.list'],
|
|
329
|
+
parallel: true,
|
|
330
|
+
},
|
|
331
|
+
},
|
|
332
|
+
|
|
333
|
+
handlers: {
|
|
334
|
+
'alerts.active': alertsActive,
|
|
335
|
+
'tests.list': testsList,
|
|
336
|
+
'network.metrics': networkMetrics,
|
|
337
|
+
'network.path-vis': networkPathVis,
|
|
338
|
+
'agents.list': agentsList,
|
|
339
|
+
'outages.network': outagesNetwork,
|
|
340
|
+
},
|
|
341
|
+
|
|
342
|
+
testConnection: async (config, credentials, fetchFn) => {
|
|
343
|
+
const url = `${config.endpoint.replace(/\/$/, '')}/v7/agents`;
|
|
344
|
+
const headers: Record<string, string> = {
|
|
345
|
+
Accept: 'application/json',
|
|
346
|
+
...buildAuthHeaders(credentials),
|
|
347
|
+
...config.headers,
|
|
348
|
+
};
|
|
349
|
+
|
|
350
|
+
const res = await fetchFn(url, { headers });
|
|
351
|
+
return res.ok;
|
|
352
|
+
},
|
|
353
|
+
};
|
|
@@ -0,0 +1,261 @@
|
|
|
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 token cache: token + expiry timestamp */
|
|
12
|
+
let cachedSession: { token: string; expiresAt: number } | null = null;
|
|
13
|
+
|
|
14
|
+
/** Acquire a vCenter session token via POST /api/session */
|
|
15
|
+
async function getSessionToken(
|
|
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.token;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const { username, password } = credentials.credentials;
|
|
26
|
+
if (!username || !password) {
|
|
27
|
+
throw new Error('vCenter requires username and password credentials');
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const encoded = Buffer.from(`${username}:${password}`).toString('base64');
|
|
31
|
+
const url = `${config.endpoint.replace(/\/$/, '')}/api/session`;
|
|
32
|
+
const res = await fetchFn(url, {
|
|
33
|
+
method: 'POST',
|
|
34
|
+
headers: {
|
|
35
|
+
Authorization: `Basic ${encoded}`,
|
|
36
|
+
Accept: 'application/json',
|
|
37
|
+
...config.headers,
|
|
38
|
+
},
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
if (!res.ok) {
|
|
42
|
+
throw new Error(`vCenter session auth failed: ${res.status} ${res.statusText}`);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const token = ((await res.json()) as string).replace(/^"|"$/g, '');
|
|
46
|
+
cachedSession = { token, expiresAt: now + 5 * 60 * 1000 };
|
|
47
|
+
return token;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// --- REST helper ---
|
|
51
|
+
|
|
52
|
+
async function vcenterGet<T>(
|
|
53
|
+
path: string,
|
|
54
|
+
config: IntegrationConfig,
|
|
55
|
+
credentials: IntegrationCredentials,
|
|
56
|
+
fetchFn: FetchFn,
|
|
57
|
+
params?: Record<string, string>,
|
|
58
|
+
): Promise<T> {
|
|
59
|
+
const token = await getSessionToken(config, credentials, fetchFn);
|
|
60
|
+
const base = `${config.endpoint.replace(/\/$/, '')}${path}`;
|
|
61
|
+
const url = new URL(base);
|
|
62
|
+
if (params) {
|
|
63
|
+
for (const [key, value] of Object.entries(params)) {
|
|
64
|
+
url.searchParams.set(key, value);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const headers: Record<string, string> = {
|
|
69
|
+
Accept: 'application/json',
|
|
70
|
+
'vmware-api-session-id': token,
|
|
71
|
+
...config.headers,
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
const res = await fetchFn(url.toString(), { headers });
|
|
75
|
+
if (!res.ok) {
|
|
76
|
+
throw new Error(`vCenter API returned ${res.status}: ${res.statusText}`);
|
|
77
|
+
}
|
|
78
|
+
return (await res.json()) as T;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// --- Probe handlers ---
|
|
82
|
+
|
|
83
|
+
const vmList: IntegrationProbeHandler = async (_params, config, credentials, fetchFn) => {
|
|
84
|
+
const vms = await vcenterGet<
|
|
85
|
+
Array<{
|
|
86
|
+
vm: string;
|
|
87
|
+
name: string;
|
|
88
|
+
power_state: string;
|
|
89
|
+
cpu_count?: number;
|
|
90
|
+
memory_size_MiB?: number;
|
|
91
|
+
}>
|
|
92
|
+
>('/api/vcenter/vm', config, credentials, fetchFn);
|
|
93
|
+
|
|
94
|
+
return {
|
|
95
|
+
vms: vms.map((vm) => ({
|
|
96
|
+
vm: vm.vm,
|
|
97
|
+
name: vm.name,
|
|
98
|
+
powerState: vm.power_state,
|
|
99
|
+
cpuCount: vm.cpu_count ?? null,
|
|
100
|
+
memorySizeMiB: vm.memory_size_MiB ?? null,
|
|
101
|
+
})),
|
|
102
|
+
count: vms.length,
|
|
103
|
+
};
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
const vmDetail: IntegrationProbeHandler = async (params, config, credentials, fetchFn) => {
|
|
107
|
+
const vm = params?.vm as string;
|
|
108
|
+
if (!vm) throw new Error('vm parameter is required (VM identifier)');
|
|
109
|
+
|
|
110
|
+
return vcenterGet(`/api/vcenter/vm/${vm}`, config, credentials, fetchFn);
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
const hostList: IntegrationProbeHandler = async (_params, config, credentials, fetchFn) => {
|
|
114
|
+
const hosts = await vcenterGet<
|
|
115
|
+
Array<{
|
|
116
|
+
host: string;
|
|
117
|
+
name: string;
|
|
118
|
+
connection_state: string;
|
|
119
|
+
power_state?: string;
|
|
120
|
+
}>
|
|
121
|
+
>('/api/vcenter/host', config, credentials, fetchFn);
|
|
122
|
+
|
|
123
|
+
return {
|
|
124
|
+
hosts: hosts.map((h) => ({
|
|
125
|
+
host: h.host,
|
|
126
|
+
name: h.name,
|
|
127
|
+
connectionState: h.connection_state,
|
|
128
|
+
powerState: h.power_state ?? null,
|
|
129
|
+
})),
|
|
130
|
+
count: hosts.length,
|
|
131
|
+
};
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
const datastoreList: IntegrationProbeHandler = async (_params, config, credentials, fetchFn) => {
|
|
135
|
+
const datastores = await vcenterGet<
|
|
136
|
+
Array<{
|
|
137
|
+
datastore: string;
|
|
138
|
+
name: string;
|
|
139
|
+
type: string;
|
|
140
|
+
capacity?: number;
|
|
141
|
+
free_space?: number;
|
|
142
|
+
}>
|
|
143
|
+
>('/api/vcenter/datastore', config, credentials, fetchFn);
|
|
144
|
+
|
|
145
|
+
return {
|
|
146
|
+
datastores: datastores.map((ds) => ({
|
|
147
|
+
datastore: ds.datastore,
|
|
148
|
+
name: ds.name,
|
|
149
|
+
type: ds.type,
|
|
150
|
+
capacity: ds.capacity ?? 0,
|
|
151
|
+
freeSpace: ds.free_space ?? 0,
|
|
152
|
+
})),
|
|
153
|
+
count: datastores.length,
|
|
154
|
+
};
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
const clusterList: IntegrationProbeHandler = async (_params, config, credentials, fetchFn) => {
|
|
158
|
+
const clusters = await vcenterGet<
|
|
159
|
+
Array<{
|
|
160
|
+
cluster: string;
|
|
161
|
+
name: string;
|
|
162
|
+
ha_enabled?: boolean;
|
|
163
|
+
drs_enabled?: boolean;
|
|
164
|
+
}>
|
|
165
|
+
>('/api/vcenter/cluster', config, credentials, fetchFn);
|
|
166
|
+
|
|
167
|
+
return {
|
|
168
|
+
clusters: clusters.map((cl) => ({
|
|
169
|
+
cluster: cl.cluster,
|
|
170
|
+
name: cl.name,
|
|
171
|
+
haEnabled: cl.ha_enabled ?? false,
|
|
172
|
+
drsEnabled: cl.drs_enabled ?? false,
|
|
173
|
+
})),
|
|
174
|
+
count: clusters.length,
|
|
175
|
+
};
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
const health: IntegrationProbeHandler = async (_params, config, credentials, fetchFn) => {
|
|
179
|
+
return vcenterGet('/api/vcenter/system/health', config, credentials, fetchFn);
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
// --- Pack definition ---
|
|
183
|
+
|
|
184
|
+
export const vcenterPack: IntegrationPack = {
|
|
185
|
+
manifest: {
|
|
186
|
+
name: 'vcenter',
|
|
187
|
+
type: 'integration',
|
|
188
|
+
version: '0.1.0',
|
|
189
|
+
description: 'VMware vCenter — VMs, ESXi hosts, datastores, clusters, and health status',
|
|
190
|
+
requires: { groups: [], files: [], commands: [] },
|
|
191
|
+
probes: [
|
|
192
|
+
{
|
|
193
|
+
name: 'vm.list',
|
|
194
|
+
description: 'List all VMs with power state, CPU, and memory',
|
|
195
|
+
capability: 'observe',
|
|
196
|
+
params: {},
|
|
197
|
+
timeout: 15000,
|
|
198
|
+
},
|
|
199
|
+
{
|
|
200
|
+
name: 'vm.detail',
|
|
201
|
+
description: 'Detailed info for a single VM by identifier',
|
|
202
|
+
capability: 'observe',
|
|
203
|
+
params: {
|
|
204
|
+
vm: { type: 'string', description: 'VM identifier (e.g. vm-42)', required: true },
|
|
205
|
+
},
|
|
206
|
+
timeout: 15000,
|
|
207
|
+
},
|
|
208
|
+
{
|
|
209
|
+
name: 'host.list',
|
|
210
|
+
description: 'List ESXi hosts with connection state',
|
|
211
|
+
capability: 'observe',
|
|
212
|
+
params: {},
|
|
213
|
+
timeout: 15000,
|
|
214
|
+
},
|
|
215
|
+
{
|
|
216
|
+
name: 'datastore.list',
|
|
217
|
+
description: 'List datastores with capacity and free space',
|
|
218
|
+
capability: 'observe',
|
|
219
|
+
params: {},
|
|
220
|
+
timeout: 15000,
|
|
221
|
+
},
|
|
222
|
+
{
|
|
223
|
+
name: 'cluster.list',
|
|
224
|
+
description: 'List clusters with HA and DRS status',
|
|
225
|
+
capability: 'observe',
|
|
226
|
+
params: {},
|
|
227
|
+
timeout: 15000,
|
|
228
|
+
},
|
|
229
|
+
{
|
|
230
|
+
name: 'health',
|
|
231
|
+
description: 'Overall vCenter health status',
|
|
232
|
+
capability: 'observe',
|
|
233
|
+
params: {},
|
|
234
|
+
timeout: 15000,
|
|
235
|
+
},
|
|
236
|
+
],
|
|
237
|
+
runbook: {
|
|
238
|
+
category: 'vmware',
|
|
239
|
+
probes: ['health', 'host.list', 'vm.list'],
|
|
240
|
+
parallel: true,
|
|
241
|
+
},
|
|
242
|
+
},
|
|
243
|
+
|
|
244
|
+
handlers: {
|
|
245
|
+
'vm.list': vmList,
|
|
246
|
+
'vm.detail': vmDetail,
|
|
247
|
+
'host.list': hostList,
|
|
248
|
+
'datastore.list': datastoreList,
|
|
249
|
+
'cluster.list': clusterList,
|
|
250
|
+
health,
|
|
251
|
+
},
|
|
252
|
+
|
|
253
|
+
testConnection: async (config, credentials, fetchFn) => {
|
|
254
|
+
try {
|
|
255
|
+
await getSessionToken(config, credentials, fetchFn);
|
|
256
|
+
return true;
|
|
257
|
+
} catch {
|
|
258
|
+
return false;
|
|
259
|
+
}
|
|
260
|
+
},
|
|
261
|
+
};
|
package/src/system/index.ts
CHANGED
|
@@ -2,8 +2,12 @@ import type { Pack } from '../types.js';
|
|
|
2
2
|
import { systemManifest } from './manifest.js';
|
|
3
3
|
import { cpuUsage } from './probes/cpu-usage.js';
|
|
4
4
|
import { diskUsage } from './probes/disk-usage.js';
|
|
5
|
+
import { logsDmesg } from './probes/logs-dmesg.js';
|
|
6
|
+
import { logsJournal } from './probes/logs-journal.js';
|
|
7
|
+
import { logsTail } from './probes/logs-tail.js';
|
|
5
8
|
import { memoryUsage } from './probes/memory-usage.js';
|
|
6
9
|
import { ping } from './probes/ping.js';
|
|
10
|
+
import { traceroute } from './probes/traceroute.js';
|
|
7
11
|
|
|
8
12
|
export const systemPack: Pack = {
|
|
9
13
|
manifest: systemManifest,
|
|
@@ -12,5 +16,9 @@ export const systemPack: Pack = {
|
|
|
12
16
|
'system.memory.usage': memoryUsage,
|
|
13
17
|
'system.cpu.usage': cpuUsage,
|
|
14
18
|
'system.network.ping': ping,
|
|
19
|
+
'system.logs.journal': logsJournal,
|
|
20
|
+
'system.logs.dmesg': logsDmesg,
|
|
21
|
+
'system.logs.tail': logsTail,
|
|
22
|
+
'system.network.traceroute': traceroute,
|
|
15
23
|
},
|
|
16
24
|
};
|