@sonde/packs 0.1.1 → 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 +4 -6
- package/.turbo/turbo-test.log +57 -814
- 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/citrix.d.ts.map +1 -1
- package/dist/integrations/citrix.js +14 -19
- package/dist/integrations/citrix.js.map +1 -1
- package/dist/integrations/citrix.test.js +3 -3
- package/dist/integrations/citrix.test.js.map +1 -1
- 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/graph.d.ts.map +1 -1
- package/dist/integrations/graph.js +10 -15
- package/dist/integrations/graph.js.map +1 -1
- package/dist/integrations/graph.test.js +3 -3
- package/dist/integrations/graph.test.js.map +1 -1
- package/dist/integrations/httpbin.d.ts.map +1 -1
- package/dist/integrations/httpbin.js +2 -7
- package/dist/integrations/httpbin.js.map +1 -1
- 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/nutanix.d.ts.map +1 -1
- package/dist/integrations/nutanix.js +12 -17
- package/dist/integrations/nutanix.js.map +1 -1
- package/dist/integrations/nutanix.test.js +3 -3
- package/dist/integrations/nutanix.test.js.map +1 -1
- 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/proxmox.d.ts.map +1 -1
- package/dist/integrations/proxmox.js +8 -13
- package/dist/integrations/proxmox.js.map +1 -1
- package/dist/integrations/proxmox.test.js +3 -3
- package/dist/integrations/proxmox.test.js.map +1 -1
- package/dist/integrations/servicenow.d.ts.map +1 -1
- package/dist/integrations/servicenow.js +10 -16
- package/dist/integrations/servicenow.js.map +1 -1
- package/dist/integrations/servicenow.test.js +3 -3
- package/dist/integrations/servicenow.test.js.map +1 -1
- package/dist/integrations/splunk.d.ts.map +1 -1
- package/dist/integrations/splunk.js +8 -13
- package/dist/integrations/splunk.js.map +1 -1
- package/dist/integrations/splunk.test.js +3 -3
- package/dist/integrations/splunk.test.js.map +1 -1
- 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 +10 -0
- package/dist/system/index.js.map +1 -1
- package/dist/system/manifest.d.ts.map +1 -1
- package/dist/system/manifest.js +100 -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/ping.d.ts +20 -0
- package/dist/system/probes/ping.d.ts.map +1 -0
- package/dist/system/probes/ping.js +54 -0
- package/dist/system/probes/ping.js.map +1 -0
- package/dist/system/probes/ping.test.d.ts +2 -0
- package/dist/system/probes/ping.test.d.ts.map +1 -0
- package/dist/system/probes/ping.test.js +127 -0
- package/dist/system/probes/ping.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/citrix.test.ts +3 -3
- package/src/integrations/citrix.ts +17 -21
- package/src/integrations/datadog.ts +281 -0
- package/src/integrations/graph.test.ts +3 -3
- package/src/integrations/graph.ts +10 -14
- package/src/integrations/httpbin.ts +2 -6
- package/src/integrations/jira.ts +272 -0
- package/src/integrations/loki.ts +228 -0
- package/src/integrations/meraki.ts +344 -0
- package/src/integrations/nutanix.test.ts +3 -3
- package/src/integrations/nutanix.ts +13 -17
- package/src/integrations/pagerduty.ts +319 -0
- package/src/integrations/proxmox.test.ts +3 -3
- package/src/integrations/proxmox.ts +8 -12
- package/src/integrations/servicenow.test.ts +3 -3
- package/src/integrations/servicenow.ts +10 -15
- package/src/integrations/splunk.test.ts +3 -3
- package/src/integrations/splunk.ts +9 -13
- package/src/integrations/thousandeyes.ts +353 -0
- package/src/integrations/vcenter.ts +261 -0
- package/src/system/index.ts +10 -0
- package/src/system/manifest.ts +113 -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/ping.test.ts +163 -0
- package/src/system/probes/ping.ts +89 -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,344 @@
|
|
|
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 apiKey = credentials.credentials.apiKey ?? '';
|
|
13
|
+
return { Authorization: `Bearer ${apiKey}` };
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// --- REST helper ---
|
|
17
|
+
|
|
18
|
+
async function merakiGet<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(/\/$/, '')}/api/v1${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(`Meraki API returned ${res.status}: ${res.statusText}`);
|
|
42
|
+
}
|
|
43
|
+
return (await res.json()) as T;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// --- Probe handlers ---
|
|
47
|
+
|
|
48
|
+
const organizationsList: IntegrationProbeHandler = async (
|
|
49
|
+
_params,
|
|
50
|
+
config,
|
|
51
|
+
credentials,
|
|
52
|
+
fetchFn,
|
|
53
|
+
) => {
|
|
54
|
+
const orgs = await merakiGet<
|
|
55
|
+
Array<{
|
|
56
|
+
id?: string;
|
|
57
|
+
name?: string;
|
|
58
|
+
url?: string;
|
|
59
|
+
}>
|
|
60
|
+
>('/organizations', config, credentials, fetchFn);
|
|
61
|
+
|
|
62
|
+
return {
|
|
63
|
+
organizations: orgs.map((o) => ({
|
|
64
|
+
id: o.id ?? null,
|
|
65
|
+
name: o.name ?? null,
|
|
66
|
+
url: o.url ?? null,
|
|
67
|
+
})),
|
|
68
|
+
count: orgs.length,
|
|
69
|
+
};
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
const devicesStatuses: IntegrationProbeHandler = async (params, config, credentials, fetchFn) => {
|
|
73
|
+
const orgId = (params?.orgId as string) || credentials.credentials.orgId;
|
|
74
|
+
if (!orgId) throw new Error('orgId parameter is required');
|
|
75
|
+
|
|
76
|
+
const devices = await merakiGet<
|
|
77
|
+
Array<{
|
|
78
|
+
name?: string;
|
|
79
|
+
serial?: string;
|
|
80
|
+
model?: string;
|
|
81
|
+
status?: string;
|
|
82
|
+
lanIp?: string;
|
|
83
|
+
mac?: string;
|
|
84
|
+
networkId?: string;
|
|
85
|
+
}>
|
|
86
|
+
>(`/organizations/${encodeURIComponent(orgId)}/devices/statuses`, config, credentials, fetchFn);
|
|
87
|
+
|
|
88
|
+
return {
|
|
89
|
+
devices: devices.map((d) => ({
|
|
90
|
+
name: d.name ?? null,
|
|
91
|
+
serial: d.serial ?? null,
|
|
92
|
+
model: d.model ?? null,
|
|
93
|
+
status: d.status ?? null,
|
|
94
|
+
lanIp: d.lanIp ?? null,
|
|
95
|
+
mac: d.mac ?? null,
|
|
96
|
+
networkId: d.networkId ?? null,
|
|
97
|
+
})),
|
|
98
|
+
count: devices.length,
|
|
99
|
+
};
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
const switchPortStatuses: IntegrationProbeHandler = async (
|
|
103
|
+
params,
|
|
104
|
+
config,
|
|
105
|
+
credentials,
|
|
106
|
+
fetchFn,
|
|
107
|
+
) => {
|
|
108
|
+
const serial = params?.serial as string;
|
|
109
|
+
if (!serial) throw new Error('serial parameter is required');
|
|
110
|
+
|
|
111
|
+
const ports = await merakiGet<
|
|
112
|
+
Array<{
|
|
113
|
+
portId?: string;
|
|
114
|
+
enabled?: boolean;
|
|
115
|
+
status?: string;
|
|
116
|
+
speed?: string;
|
|
117
|
+
duplex?: string;
|
|
118
|
+
cdp?: Record<string, unknown>;
|
|
119
|
+
lldp?: Record<string, unknown>;
|
|
120
|
+
errors?: string[];
|
|
121
|
+
usageInKb?: { sent?: number; recv?: number };
|
|
122
|
+
}>
|
|
123
|
+
>(`/devices/${encodeURIComponent(serial)}/switch/ports/statuses`, config, credentials, fetchFn);
|
|
124
|
+
|
|
125
|
+
return {
|
|
126
|
+
ports: ports.map((p) => ({
|
|
127
|
+
portId: p.portId ?? null,
|
|
128
|
+
enabled: p.enabled ?? false,
|
|
129
|
+
status: p.status ?? null,
|
|
130
|
+
speed: p.speed ?? null,
|
|
131
|
+
duplex: p.duplex ?? null,
|
|
132
|
+
cdp: p.cdp ?? null,
|
|
133
|
+
lldp: p.lldp ?? null,
|
|
134
|
+
errors: p.errors ?? [],
|
|
135
|
+
usage: p.usageInKb ?? null,
|
|
136
|
+
})),
|
|
137
|
+
count: ports.length,
|
|
138
|
+
};
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
const deviceLldpCdp: IntegrationProbeHandler = async (params, config, credentials, fetchFn) => {
|
|
142
|
+
const serial = params?.serial as string;
|
|
143
|
+
if (!serial) throw new Error('serial parameter is required');
|
|
144
|
+
|
|
145
|
+
const data = await merakiGet<{
|
|
146
|
+
ports?: Record<
|
|
147
|
+
string,
|
|
148
|
+
{
|
|
149
|
+
cdp?: { deviceId?: string; platform?: string; portId?: string; address?: string };
|
|
150
|
+
lldp?: { systemName?: string; portId?: string; managementAddress?: string };
|
|
151
|
+
}
|
|
152
|
+
>;
|
|
153
|
+
}>(`/devices/${encodeURIComponent(serial)}/lldpCdp`, config, credentials, fetchFn);
|
|
154
|
+
|
|
155
|
+
const ports = data.ports ?? {};
|
|
156
|
+
const neighbors = Object.entries(ports).map(([portId, info]) => ({
|
|
157
|
+
portId,
|
|
158
|
+
cdp: info.cdp ?? null,
|
|
159
|
+
lldp: info.lldp ?? null,
|
|
160
|
+
}));
|
|
161
|
+
|
|
162
|
+
return { neighbors, count: neighbors.length };
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
const deviceClients: IntegrationProbeHandler = async (params, config, credentials, fetchFn) => {
|
|
166
|
+
const serial = params?.serial as string;
|
|
167
|
+
if (!serial) throw new Error('serial parameter is required');
|
|
168
|
+
|
|
169
|
+
const queryParams: Record<string, string> = {};
|
|
170
|
+
const timespan = params?.timespan as number | undefined;
|
|
171
|
+
if (timespan) queryParams.timespan = String(timespan);
|
|
172
|
+
|
|
173
|
+
const clients = await merakiGet<
|
|
174
|
+
Array<{
|
|
175
|
+
mac?: string;
|
|
176
|
+
ip?: string;
|
|
177
|
+
description?: string;
|
|
178
|
+
vlan?: number;
|
|
179
|
+
switchport?: string;
|
|
180
|
+
usage?: { sent?: number; recv?: number };
|
|
181
|
+
}>
|
|
182
|
+
>(
|
|
183
|
+
`/devices/${encodeURIComponent(serial)}/clients`,
|
|
184
|
+
config,
|
|
185
|
+
credentials,
|
|
186
|
+
fetchFn,
|
|
187
|
+
queryParams,
|
|
188
|
+
);
|
|
189
|
+
|
|
190
|
+
return {
|
|
191
|
+
clients: clients.map((c) => ({
|
|
192
|
+
mac: c.mac ?? null,
|
|
193
|
+
ip: c.ip ?? null,
|
|
194
|
+
description: c.description ?? null,
|
|
195
|
+
vlan: c.vlan ?? null,
|
|
196
|
+
switchport: c.switchport ?? null,
|
|
197
|
+
usage: c.usage ?? null,
|
|
198
|
+
})),
|
|
199
|
+
count: clients.length,
|
|
200
|
+
};
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
const networksList: IntegrationProbeHandler = async (params, config, credentials, fetchFn) => {
|
|
204
|
+
const orgId = (params?.orgId as string) || credentials.credentials.orgId;
|
|
205
|
+
if (!orgId) throw new Error('orgId parameter is required');
|
|
206
|
+
|
|
207
|
+
const networks = await merakiGet<
|
|
208
|
+
Array<{
|
|
209
|
+
id?: string;
|
|
210
|
+
name?: string;
|
|
211
|
+
productTypes?: string[];
|
|
212
|
+
timeZone?: string;
|
|
213
|
+
tags?: string[];
|
|
214
|
+
}>
|
|
215
|
+
>(`/organizations/${encodeURIComponent(orgId)}/networks`, config, credentials, fetchFn);
|
|
216
|
+
|
|
217
|
+
return {
|
|
218
|
+
networks: networks.map((n) => ({
|
|
219
|
+
id: n.id ?? null,
|
|
220
|
+
name: n.name ?? null,
|
|
221
|
+
productTypes: n.productTypes ?? [],
|
|
222
|
+
timeZone: n.timeZone ?? null,
|
|
223
|
+
tags: n.tags ?? [],
|
|
224
|
+
})),
|
|
225
|
+
count: networks.length,
|
|
226
|
+
};
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
// --- Pack definition ---
|
|
230
|
+
|
|
231
|
+
export const merakiPack: IntegrationPack = {
|
|
232
|
+
manifest: {
|
|
233
|
+
name: 'meraki',
|
|
234
|
+
type: 'integration',
|
|
235
|
+
version: '0.1.0',
|
|
236
|
+
description: 'Cisco Meraki — device fleet status, switch ports, and network topology',
|
|
237
|
+
requires: { groups: [], files: [], commands: [] },
|
|
238
|
+
probes: [
|
|
239
|
+
{
|
|
240
|
+
name: 'organizations.list',
|
|
241
|
+
description: 'List organizations accessible by the API key',
|
|
242
|
+
capability: 'observe',
|
|
243
|
+
params: {},
|
|
244
|
+
timeout: 15000,
|
|
245
|
+
},
|
|
246
|
+
{
|
|
247
|
+
name: 'devices.statuses',
|
|
248
|
+
description: 'List device statuses across an organization',
|
|
249
|
+
capability: 'observe',
|
|
250
|
+
params: {
|
|
251
|
+
orgId: {
|
|
252
|
+
type: 'string',
|
|
253
|
+
description: 'Meraki organization ID (falls back to credential orgId)',
|
|
254
|
+
required: false,
|
|
255
|
+
},
|
|
256
|
+
},
|
|
257
|
+
timeout: 15000,
|
|
258
|
+
},
|
|
259
|
+
{
|
|
260
|
+
name: 'switch.port-statuses',
|
|
261
|
+
description: 'Get port statuses for a switch including speed, duplex, and neighbors',
|
|
262
|
+
capability: 'observe',
|
|
263
|
+
params: {
|
|
264
|
+
serial: {
|
|
265
|
+
type: 'string',
|
|
266
|
+
description: 'Device serial number',
|
|
267
|
+
required: true,
|
|
268
|
+
},
|
|
269
|
+
},
|
|
270
|
+
timeout: 15000,
|
|
271
|
+
},
|
|
272
|
+
{
|
|
273
|
+
name: 'device.lldp-cdp',
|
|
274
|
+
description: 'Get LLDP and CDP neighbor information for a device',
|
|
275
|
+
capability: 'observe',
|
|
276
|
+
params: {
|
|
277
|
+
serial: {
|
|
278
|
+
type: 'string',
|
|
279
|
+
description: 'Device serial number',
|
|
280
|
+
required: true,
|
|
281
|
+
},
|
|
282
|
+
},
|
|
283
|
+
timeout: 15000,
|
|
284
|
+
},
|
|
285
|
+
{
|
|
286
|
+
name: 'device.clients',
|
|
287
|
+
description: 'List clients connected to a device',
|
|
288
|
+
capability: 'observe',
|
|
289
|
+
params: {
|
|
290
|
+
serial: {
|
|
291
|
+
type: 'string',
|
|
292
|
+
description: 'Device serial number',
|
|
293
|
+
required: true,
|
|
294
|
+
},
|
|
295
|
+
timespan: {
|
|
296
|
+
type: 'number',
|
|
297
|
+
description: 'Lookback period in seconds (default: 86400)',
|
|
298
|
+
required: false,
|
|
299
|
+
},
|
|
300
|
+
},
|
|
301
|
+
timeout: 15000,
|
|
302
|
+
},
|
|
303
|
+
{
|
|
304
|
+
name: 'networks.list',
|
|
305
|
+
description: 'List networks in an organization',
|
|
306
|
+
capability: 'observe',
|
|
307
|
+
params: {
|
|
308
|
+
orgId: {
|
|
309
|
+
type: 'string',
|
|
310
|
+
description: 'Meraki organization ID (falls back to credential orgId)',
|
|
311
|
+
required: false,
|
|
312
|
+
},
|
|
313
|
+
},
|
|
314
|
+
timeout: 15000,
|
|
315
|
+
},
|
|
316
|
+
],
|
|
317
|
+
runbook: {
|
|
318
|
+
category: 'network',
|
|
319
|
+
probes: ['organizations.list', 'devices.statuses'],
|
|
320
|
+
parallel: true,
|
|
321
|
+
},
|
|
322
|
+
},
|
|
323
|
+
|
|
324
|
+
handlers: {
|
|
325
|
+
'organizations.list': organizationsList,
|
|
326
|
+
'devices.statuses': devicesStatuses,
|
|
327
|
+
'switch.port-statuses': switchPortStatuses,
|
|
328
|
+
'device.lldp-cdp': deviceLldpCdp,
|
|
329
|
+
'device.clients': deviceClients,
|
|
330
|
+
'networks.list': networksList,
|
|
331
|
+
},
|
|
332
|
+
|
|
333
|
+
testConnection: async (config, credentials, fetchFn) => {
|
|
334
|
+
const url = `${config.endpoint.replace(/\/$/, '')}/api/v1/organizations`;
|
|
335
|
+
const headers: Record<string, string> = {
|
|
336
|
+
Accept: 'application/json',
|
|
337
|
+
...buildAuthHeaders(credentials),
|
|
338
|
+
...config.headers,
|
|
339
|
+
};
|
|
340
|
+
|
|
341
|
+
const res = await fetchFn(url, { headers });
|
|
342
|
+
return res.ok;
|
|
343
|
+
},
|
|
344
|
+
};
|
|
@@ -924,10 +924,10 @@ describe('nutanix pack', () => {
|
|
|
924
924
|
expect(result).toBe(false);
|
|
925
925
|
});
|
|
926
926
|
|
|
927
|
-
it('
|
|
927
|
+
it('throws on network error', async () => {
|
|
928
928
|
const fetchFn = vi.fn().mockRejectedValue(new Error('ECONNREFUSED'));
|
|
929
|
-
|
|
930
|
-
|
|
929
|
+
await expect(nutanixPack.testConnection(ntnxConfig, basicCreds, fetchFn))
|
|
930
|
+
.rejects.toThrow('ECONNREFUSED');
|
|
931
931
|
});
|
|
932
932
|
|
|
933
933
|
it('returns false when data is null', async () => {
|
|
@@ -1438,23 +1438,19 @@ export const nutanixPack: IntegrationPack = {
|
|
|
1438
1438
|
},
|
|
1439
1439
|
|
|
1440
1440
|
testConnection: async (config, credentials, fetchFn) => {
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
};
|
|
1441
|
+
const url = nutanixUrl(config.endpoint, 'clustermgmt', 'config/clusters');
|
|
1442
|
+
const fullUrl = new URL(url);
|
|
1443
|
+
fullUrl.searchParams.set('$limit', '1');
|
|
1444
|
+
|
|
1445
|
+
const headers: Record<string, string> = {
|
|
1446
|
+
Accept: 'application/json',
|
|
1447
|
+
...buildAuthHeaders(credentials),
|
|
1448
|
+
...config.headers,
|
|
1449
|
+
};
|
|
1451
1450
|
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
} catch {
|
|
1457
|
-
return false;
|
|
1458
|
-
}
|
|
1451
|
+
const res = await fetchFn(fullUrl.toString(), { headers });
|
|
1452
|
+
if (!res.ok) return false;
|
|
1453
|
+
const body = (await res.json()) as { data?: unknown };
|
|
1454
|
+
return body.data != null;
|
|
1459
1455
|
},
|
|
1460
1456
|
};
|
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
FetchFn,
|
|
3
|
+
IntegrationConfig,
|
|
4
|
+
IntegrationCredentials,
|
|
5
|
+
IntegrationPack,
|
|
6
|
+
IntegrationProbeHandler,
|
|
7
|
+
} from '@sonde/shared';
|
|
8
|
+
|
|
9
|
+
// --- Auth helpers ---
|
|
10
|
+
|
|
11
|
+
/** Build PagerDuty auth header: Token token=<key> */
|
|
12
|
+
function buildAuthHeaders(credentials: IntegrationCredentials): Record<string, string> {
|
|
13
|
+
const token = credentials.credentials.token ?? '';
|
|
14
|
+
return { Authorization: `Token token=${token}` };
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// --- REST helper ---
|
|
18
|
+
|
|
19
|
+
async function pagerdutyGet<T>(
|
|
20
|
+
path: string,
|
|
21
|
+
config: IntegrationConfig,
|
|
22
|
+
credentials: IntegrationCredentials,
|
|
23
|
+
fetchFn: FetchFn,
|
|
24
|
+
params?: Record<string, string>,
|
|
25
|
+
): Promise<T> {
|
|
26
|
+
const base = `${config.endpoint.replace(/\/$/, '')}${path}`;
|
|
27
|
+
const url = new URL(base);
|
|
28
|
+
if (params) {
|
|
29
|
+
for (const [key, value] of Object.entries(params)) {
|
|
30
|
+
url.searchParams.set(key, value);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const headers: Record<string, string> = {
|
|
35
|
+
Accept: 'application/json',
|
|
36
|
+
'Content-Type': 'application/json',
|
|
37
|
+
...buildAuthHeaders(credentials),
|
|
38
|
+
...config.headers,
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const res = await fetchFn(url.toString(), { headers });
|
|
42
|
+
if (!res.ok) {
|
|
43
|
+
throw new Error(`PagerDuty API returned ${res.status}: ${res.statusText}`);
|
|
44
|
+
}
|
|
45
|
+
return (await res.json()) as T;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// --- Probe handlers ---
|
|
49
|
+
|
|
50
|
+
const incidentsList: IntegrationProbeHandler = async (params, config, credentials, fetchFn) => {
|
|
51
|
+
const queryParams: Record<string, string> = {};
|
|
52
|
+
const status = params?.status as string | undefined;
|
|
53
|
+
if (status) queryParams['statuses[]'] = status;
|
|
54
|
+
const since = params?.since as string | undefined;
|
|
55
|
+
if (since) queryParams.since = since;
|
|
56
|
+
const until = params?.until as string | undefined;
|
|
57
|
+
if (until) queryParams.until = until;
|
|
58
|
+
|
|
59
|
+
const data = await pagerdutyGet<{
|
|
60
|
+
incidents?: Array<{
|
|
61
|
+
id: string;
|
|
62
|
+
incident_number?: number;
|
|
63
|
+
title?: string;
|
|
64
|
+
status?: string;
|
|
65
|
+
urgency?: string;
|
|
66
|
+
created_at?: string;
|
|
67
|
+
service?: { id?: string; summary?: string };
|
|
68
|
+
assignees?: Array<{ summary?: string }>;
|
|
69
|
+
}>;
|
|
70
|
+
total?: number;
|
|
71
|
+
}>('/incidents', config, credentials, fetchFn, queryParams);
|
|
72
|
+
|
|
73
|
+
const incidents = data.incidents ?? [];
|
|
74
|
+
return {
|
|
75
|
+
incidents: incidents.map((i) => ({
|
|
76
|
+
id: i.id,
|
|
77
|
+
incidentNumber: i.incident_number ?? null,
|
|
78
|
+
title: i.title ?? null,
|
|
79
|
+
status: i.status ?? null,
|
|
80
|
+
urgency: i.urgency ?? null,
|
|
81
|
+
createdAt: i.created_at ?? null,
|
|
82
|
+
service: i.service?.summary ?? null,
|
|
83
|
+
assignees: (i.assignees ?? []).map((a) => a.summary ?? ''),
|
|
84
|
+
})),
|
|
85
|
+
total: data.total ?? incidents.length,
|
|
86
|
+
count: incidents.length,
|
|
87
|
+
};
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
const incidentsTriggered: IntegrationProbeHandler = async (
|
|
91
|
+
_params,
|
|
92
|
+
config,
|
|
93
|
+
credentials,
|
|
94
|
+
fetchFn,
|
|
95
|
+
) => {
|
|
96
|
+
const data = await pagerdutyGet<{
|
|
97
|
+
incidents?: Array<{
|
|
98
|
+
id: string;
|
|
99
|
+
incident_number?: number;
|
|
100
|
+
title?: string;
|
|
101
|
+
status?: string;
|
|
102
|
+
urgency?: string;
|
|
103
|
+
created_at?: string;
|
|
104
|
+
service?: { id?: string; summary?: string };
|
|
105
|
+
assignees?: Array<{ summary?: string }>;
|
|
106
|
+
}>;
|
|
107
|
+
total?: number;
|
|
108
|
+
}>('/incidents', config, credentials, fetchFn, {
|
|
109
|
+
'statuses[]': 'triggered,acknowledged',
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
const incidents = data.incidents ?? [];
|
|
113
|
+
return {
|
|
114
|
+
incidents: incidents.map((i) => ({
|
|
115
|
+
id: i.id,
|
|
116
|
+
incidentNumber: i.incident_number ?? null,
|
|
117
|
+
title: i.title ?? null,
|
|
118
|
+
status: i.status ?? null,
|
|
119
|
+
urgency: i.urgency ?? null,
|
|
120
|
+
createdAt: i.created_at ?? null,
|
|
121
|
+
service: i.service?.summary ?? null,
|
|
122
|
+
assignees: (i.assignees ?? []).map((a) => a.summary ?? ''),
|
|
123
|
+
})),
|
|
124
|
+
total: data.total ?? incidents.length,
|
|
125
|
+
count: incidents.length,
|
|
126
|
+
};
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
const servicesList: IntegrationProbeHandler = async (_params, config, credentials, fetchFn) => {
|
|
130
|
+
const data = await pagerdutyGet<{
|
|
131
|
+
services?: Array<{
|
|
132
|
+
id: string;
|
|
133
|
+
name?: string;
|
|
134
|
+
status?: string;
|
|
135
|
+
description?: string;
|
|
136
|
+
created_at?: string;
|
|
137
|
+
}>;
|
|
138
|
+
total?: number;
|
|
139
|
+
}>('/services', config, credentials, fetchFn);
|
|
140
|
+
|
|
141
|
+
const services = data.services ?? [];
|
|
142
|
+
return {
|
|
143
|
+
services: services.map((s) => ({
|
|
144
|
+
id: s.id,
|
|
145
|
+
name: s.name ?? null,
|
|
146
|
+
status: s.status ?? null,
|
|
147
|
+
description: s.description ?? null,
|
|
148
|
+
createdAt: s.created_at ?? null,
|
|
149
|
+
})),
|
|
150
|
+
count: services.length,
|
|
151
|
+
};
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
const serviceDetail: IntegrationProbeHandler = async (params, config, credentials, fetchFn) => {
|
|
155
|
+
const id = params?.id as string;
|
|
156
|
+
if (!id) throw new Error('id parameter is required (service ID)');
|
|
157
|
+
|
|
158
|
+
const data = await pagerdutyGet<{
|
|
159
|
+
service?: {
|
|
160
|
+
id: string;
|
|
161
|
+
name?: string;
|
|
162
|
+
status?: string;
|
|
163
|
+
description?: string;
|
|
164
|
+
escalation_policy?: { id?: string; summary?: string };
|
|
165
|
+
integrations?: Array<{ id?: string; summary?: string; type?: string }>;
|
|
166
|
+
};
|
|
167
|
+
}>(`/services/${id}`, config, credentials, fetchFn);
|
|
168
|
+
|
|
169
|
+
const svc = data.service;
|
|
170
|
+
if (!svc) throw new Error(`Service ${id} not found`);
|
|
171
|
+
|
|
172
|
+
return {
|
|
173
|
+
id: svc.id,
|
|
174
|
+
name: svc.name ?? null,
|
|
175
|
+
status: svc.status ?? null,
|
|
176
|
+
description: svc.description ?? null,
|
|
177
|
+
escalationPolicy: svc.escalation_policy?.summary ?? null,
|
|
178
|
+
integrations: (svc.integrations ?? []).map((i) => ({
|
|
179
|
+
id: i.id ?? null,
|
|
180
|
+
summary: i.summary ?? null,
|
|
181
|
+
type: i.type ?? null,
|
|
182
|
+
})),
|
|
183
|
+
};
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
const oncallList: IntegrationProbeHandler = async (params, config, credentials, fetchFn) => {
|
|
187
|
+
const queryParams: Record<string, string> = {};
|
|
188
|
+
const scheduleIds = params?.schedule_ids as string | undefined;
|
|
189
|
+
if (scheduleIds) queryParams['schedule_ids[]'] = scheduleIds;
|
|
190
|
+
|
|
191
|
+
const data = await pagerdutyGet<{
|
|
192
|
+
oncalls?: Array<{
|
|
193
|
+
user?: { id?: string; summary?: string };
|
|
194
|
+
schedule?: { id?: string; summary?: string };
|
|
195
|
+
escalation_policy?: { id?: string; summary?: string };
|
|
196
|
+
escalation_level?: number;
|
|
197
|
+
start?: string;
|
|
198
|
+
end?: string;
|
|
199
|
+
}>;
|
|
200
|
+
}>('/oncalls', config, credentials, fetchFn, queryParams);
|
|
201
|
+
|
|
202
|
+
const oncalls = data.oncalls ?? [];
|
|
203
|
+
return {
|
|
204
|
+
oncalls: oncalls.map((o) => ({
|
|
205
|
+
user: o.user?.summary ?? null,
|
|
206
|
+
schedule: o.schedule?.summary ?? null,
|
|
207
|
+
escalationPolicy: o.escalation_policy?.summary ?? null,
|
|
208
|
+
escalationLevel: o.escalation_level ?? null,
|
|
209
|
+
start: o.start ?? null,
|
|
210
|
+
end: o.end ?? null,
|
|
211
|
+
})),
|
|
212
|
+
count: oncalls.length,
|
|
213
|
+
};
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
// --- Pack definition ---
|
|
217
|
+
|
|
218
|
+
export const pagerdutyPack: IntegrationPack = {
|
|
219
|
+
manifest: {
|
|
220
|
+
name: 'pagerduty',
|
|
221
|
+
type: 'integration',
|
|
222
|
+
version: '0.1.0',
|
|
223
|
+
description:
|
|
224
|
+
'PagerDuty — incidents, services, on-call schedules',
|
|
225
|
+
requires: { groups: [], files: [], commands: [] },
|
|
226
|
+
probes: [
|
|
227
|
+
{
|
|
228
|
+
name: 'incidents.list',
|
|
229
|
+
description: 'List incidents with optional status, since, and until filters',
|
|
230
|
+
capability: 'observe',
|
|
231
|
+
params: {
|
|
232
|
+
status: {
|
|
233
|
+
type: 'string',
|
|
234
|
+
description:
|
|
235
|
+
'Filter by status (triggered, acknowledged, resolved)',
|
|
236
|
+
required: false,
|
|
237
|
+
},
|
|
238
|
+
since: {
|
|
239
|
+
type: 'string',
|
|
240
|
+
description: 'Start date (ISO 8601)',
|
|
241
|
+
required: false,
|
|
242
|
+
},
|
|
243
|
+
until: {
|
|
244
|
+
type: 'string',
|
|
245
|
+
description: 'End date (ISO 8601)',
|
|
246
|
+
required: false,
|
|
247
|
+
},
|
|
248
|
+
},
|
|
249
|
+
timeout: 15000,
|
|
250
|
+
},
|
|
251
|
+
{
|
|
252
|
+
name: 'incidents.triggered',
|
|
253
|
+
description: 'List active incidents (triggered + acknowledged)',
|
|
254
|
+
capability: 'observe',
|
|
255
|
+
params: {},
|
|
256
|
+
timeout: 15000,
|
|
257
|
+
},
|
|
258
|
+
{
|
|
259
|
+
name: 'services.list',
|
|
260
|
+
description: 'List services with status',
|
|
261
|
+
capability: 'observe',
|
|
262
|
+
params: {},
|
|
263
|
+
timeout: 15000,
|
|
264
|
+
},
|
|
265
|
+
{
|
|
266
|
+
name: 'service.detail',
|
|
267
|
+
description: 'Service detail with integrations and escalation policy',
|
|
268
|
+
capability: 'observe',
|
|
269
|
+
params: {
|
|
270
|
+
id: {
|
|
271
|
+
type: 'string',
|
|
272
|
+
description: 'Service ID',
|
|
273
|
+
required: true,
|
|
274
|
+
},
|
|
275
|
+
},
|
|
276
|
+
timeout: 15000,
|
|
277
|
+
},
|
|
278
|
+
{
|
|
279
|
+
name: 'oncall.list',
|
|
280
|
+
description: 'Current on-call schedules',
|
|
281
|
+
capability: 'observe',
|
|
282
|
+
params: {
|
|
283
|
+
schedule_ids: {
|
|
284
|
+
type: 'string',
|
|
285
|
+
description: 'Comma-separated schedule IDs to filter',
|
|
286
|
+
required: false,
|
|
287
|
+
},
|
|
288
|
+
},
|
|
289
|
+
timeout: 15000,
|
|
290
|
+
},
|
|
291
|
+
],
|
|
292
|
+
runbook: {
|
|
293
|
+
category: 'itsm',
|
|
294
|
+
probes: ['incidents.triggered', 'services.list'],
|
|
295
|
+
parallel: true,
|
|
296
|
+
},
|
|
297
|
+
},
|
|
298
|
+
|
|
299
|
+
handlers: {
|
|
300
|
+
'incidents.list': incidentsList,
|
|
301
|
+
'incidents.triggered': incidentsTriggered,
|
|
302
|
+
'services.list': servicesList,
|
|
303
|
+
'service.detail': serviceDetail,
|
|
304
|
+
'oncall.list': oncallList,
|
|
305
|
+
},
|
|
306
|
+
|
|
307
|
+
testConnection: async (config, credentials, fetchFn) => {
|
|
308
|
+
const url = `${config.endpoint.replace(/\/$/, '')}/abilities`;
|
|
309
|
+
const headers: Record<string, string> = {
|
|
310
|
+
Accept: 'application/json',
|
|
311
|
+
'Content-Type': 'application/json',
|
|
312
|
+
...buildAuthHeaders(credentials),
|
|
313
|
+
...config.headers,
|
|
314
|
+
};
|
|
315
|
+
|
|
316
|
+
const res = await fetchFn(url, { headers });
|
|
317
|
+
return res.ok;
|
|
318
|
+
},
|
|
319
|
+
};
|