kubeagent 0.1.43 → 0.1.45
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/package.json +5 -2
- package/dist/auth.d.ts +0 -29
- package/dist/check/index.d.ts +0 -34
- package/dist/check/index.test.d.ts +0 -1
- package/dist/check/index.test.js +0 -202
- package/dist/check/run.d.ts +0 -10
- package/dist/cli.d.ts +0 -2
- package/dist/config.d.ts +0 -75
- package/dist/debug.d.ts +0 -10
- package/dist/detector/cache.d.ts +0 -6
- package/dist/detector/cache.test.d.ts +0 -1
- package/dist/detector/cache.test.js +0 -54
- package/dist/detector/catalog.d.ts +0 -17
- package/dist/detector/catalog.test.d.ts +0 -1
- package/dist/detector/catalog.test.js +0 -33
- package/dist/detector/eol.d.ts +0 -13
- package/dist/detector/eol.test.d.ts +0 -1
- package/dist/detector/eol.test.js +0 -78
- package/dist/detector/image-parser.d.ts +0 -11
- package/dist/detector/image-parser.test.d.ts +0 -1
- package/dist/detector/image-parser.test.js +0 -87
- package/dist/detector/index.d.ts +0 -33
- package/dist/detector/index.test.d.ts +0 -1
- package/dist/detector/index.test.js +0 -210
- package/dist/detector/osv.d.ts +0 -12
- package/dist/detector/osv.test.d.ts +0 -1
- package/dist/detector/osv.test.js +0 -86
- package/dist/detector/report.d.ts +0 -6
- package/dist/detector/sources.d.ts +0 -55
- package/dist/detector/sources.test.d.ts +0 -1
- package/dist/detector/sources.test.js +0 -36
- package/dist/diagnoser/approval.test.d.ts +0 -1
- package/dist/diagnoser/approval.test.js +0 -49
- package/dist/diagnoser/index.d.ts +0 -33
- package/dist/diagnoser/tools.d.ts +0 -119
- package/dist/kb/loader.d.ts +0 -1
- package/dist/kb/writer.d.ts +0 -12
- package/dist/kubectl-config.d.ts +0 -7
- package/dist/kubectl.d.ts +0 -13
- package/dist/monitor/checks.d.ts +0 -71
- package/dist/monitor/index.d.ts +0 -27
- package/dist/monitor/resolve.test.d.ts +0 -1
- package/dist/monitor/resolve.test.js +0 -21
- package/dist/monitor/scope.test.d.ts +0 -1
- package/dist/monitor/scope.test.js +0 -87
- package/dist/monitor/types.d.ts +0 -11
- package/dist/notify/discord.d.ts +0 -4
- package/dist/notify/index.d.ts +0 -6
- package/dist/notify/pagerduty.d.ts +0 -9
- package/dist/notify/pagerduty.test.d.ts +0 -1
- package/dist/notify/pagerduty.test.js +0 -97
- package/dist/notify/setup.d.ts +0 -9
- package/dist/notify/slack.d.ts +0 -4
- package/dist/notify/ssrf-guard.d.ts +0 -5
- package/dist/notify/teams.d.ts +0 -3
- package/dist/notify/telegram.d.ts +0 -9
- package/dist/notify/webhook.d.ts +0 -3
- package/dist/notify/webhook.test.d.ts +0 -1
- package/dist/notify/webhook.test.js +0 -74
- package/dist/onboard/cluster-scan.d.ts +0 -42
- package/dist/onboard/code-scan.d.ts +0 -9
- package/dist/onboard/index.d.ts +0 -3
- package/dist/onboard/interview.d.ts +0 -12
- package/dist/onboard/project-matcher.d.ts +0 -25
- package/dist/orchestrator.d.ts +0 -3
- package/dist/proxy-client.d.ts +0 -27
- package/dist/render.d.ts +0 -5
- package/dist/telemetry.d.ts +0 -1
- package/dist/telemetry.test.d.ts +0 -1
- package/dist/telemetry.test.js +0 -18
- package/dist/update-notifier.d.ts +0 -1
- package/dist/update-notifier.test.d.ts +0 -1
- package/dist/update-notifier.test.js +0 -50
- package/dist/verifier.d.ts +0 -9
- package/dist/version-check.d.ts +0 -14
- package/dist/version-check.test.d.ts +0 -1
- package/dist/version-check.test.js +0 -179
|
@@ -1,119 +0,0 @@
|
|
|
1
|
-
import { z } from "zod";
|
|
2
|
-
export declare const safeActions: Set<string>;
|
|
3
|
-
export declare const riskyActions: Set<string>;
|
|
4
|
-
export declare function isActionSafe(action: string): boolean;
|
|
5
|
-
export declare const kubectlGetLogsSchema: z.ZodObject<{
|
|
6
|
-
namespace: z.ZodString;
|
|
7
|
-
pod: z.ZodString;
|
|
8
|
-
container: z.ZodOptional<z.ZodString>;
|
|
9
|
-
tail: z.ZodDefault<z.ZodNumber>;
|
|
10
|
-
}, "strip", z.ZodTypeAny, {
|
|
11
|
-
namespace: string;
|
|
12
|
-
pod: string;
|
|
13
|
-
tail: number;
|
|
14
|
-
container?: string | undefined;
|
|
15
|
-
}, {
|
|
16
|
-
namespace: string;
|
|
17
|
-
pod: string;
|
|
18
|
-
container?: string | undefined;
|
|
19
|
-
tail?: number | undefined;
|
|
20
|
-
}>;
|
|
21
|
-
export declare const kubectlDescribeSchema: z.ZodObject<{
|
|
22
|
-
namespace: z.ZodString;
|
|
23
|
-
resource_type: z.ZodEnum<["pod", "deployment", "service", "statefulset", "daemonset", "replicaset", "job", "cronjob", "ingress", "node", "persistentvolumeclaim", "configmap", "endpoints"]>;
|
|
24
|
-
name: z.ZodString;
|
|
25
|
-
}, "strip", z.ZodTypeAny, {
|
|
26
|
-
name: string;
|
|
27
|
-
namespace: string;
|
|
28
|
-
resource_type: "service" | "node" | "pod" | "deployment" | "statefulset" | "daemonset" | "replicaset" | "job" | "cronjob" | "ingress" | "persistentvolumeclaim" | "configmap" | "endpoints";
|
|
29
|
-
}, {
|
|
30
|
-
name: string;
|
|
31
|
-
namespace: string;
|
|
32
|
-
resource_type: "service" | "node" | "pod" | "deployment" | "statefulset" | "daemonset" | "replicaset" | "job" | "cronjob" | "ingress" | "persistentvolumeclaim" | "configmap" | "endpoints";
|
|
33
|
-
}>;
|
|
34
|
-
export declare const restartPodSchema: z.ZodObject<{
|
|
35
|
-
namespace: z.ZodString;
|
|
36
|
-
pod: z.ZodString;
|
|
37
|
-
}, "strip", z.ZodTypeAny, {
|
|
38
|
-
namespace: string;
|
|
39
|
-
pod: string;
|
|
40
|
-
}, {
|
|
41
|
-
namespace: string;
|
|
42
|
-
pod: string;
|
|
43
|
-
}>;
|
|
44
|
-
export declare const rolloutRestartSchema: z.ZodObject<{
|
|
45
|
-
namespace: z.ZodString;
|
|
46
|
-
deployment: z.ZodString;
|
|
47
|
-
}, "strip", z.ZodTypeAny, {
|
|
48
|
-
namespace: string;
|
|
49
|
-
deployment: string;
|
|
50
|
-
}, {
|
|
51
|
-
namespace: string;
|
|
52
|
-
deployment: string;
|
|
53
|
-
}>;
|
|
54
|
-
export declare const scaleDeploymentSchema: z.ZodObject<{
|
|
55
|
-
namespace: z.ZodString;
|
|
56
|
-
deployment: z.ZodString;
|
|
57
|
-
replicas: z.ZodNumber;
|
|
58
|
-
}, "strip", z.ZodTypeAny, {
|
|
59
|
-
namespace: string;
|
|
60
|
-
deployment: string;
|
|
61
|
-
replicas: number;
|
|
62
|
-
}, {
|
|
63
|
-
namespace: string;
|
|
64
|
-
deployment: string;
|
|
65
|
-
replicas: number;
|
|
66
|
-
}>;
|
|
67
|
-
export declare const getEventsSchema: z.ZodObject<{
|
|
68
|
-
namespace: z.ZodString;
|
|
69
|
-
}, "strip", z.ZodTypeAny, {
|
|
70
|
-
namespace: string;
|
|
71
|
-
}, {
|
|
72
|
-
namespace: string;
|
|
73
|
-
}>;
|
|
74
|
-
export declare const setResourcesSchema: z.ZodObject<{
|
|
75
|
-
namespace: z.ZodString;
|
|
76
|
-
deployment: z.ZodString;
|
|
77
|
-
container: z.ZodOptional<z.ZodString>;
|
|
78
|
-
memory_request: z.ZodOptional<z.ZodString>;
|
|
79
|
-
memory_limit: z.ZodOptional<z.ZodString>;
|
|
80
|
-
cpu_request: z.ZodOptional<z.ZodString>;
|
|
81
|
-
cpu_limit: z.ZodOptional<z.ZodString>;
|
|
82
|
-
}, "strip", z.ZodTypeAny, {
|
|
83
|
-
namespace: string;
|
|
84
|
-
deployment: string;
|
|
85
|
-
container?: string | undefined;
|
|
86
|
-
memory_request?: string | undefined;
|
|
87
|
-
memory_limit?: string | undefined;
|
|
88
|
-
cpu_request?: string | undefined;
|
|
89
|
-
cpu_limit?: string | undefined;
|
|
90
|
-
}, {
|
|
91
|
-
namespace: string;
|
|
92
|
-
deployment: string;
|
|
93
|
-
container?: string | undefined;
|
|
94
|
-
memory_request?: string | undefined;
|
|
95
|
-
memory_limit?: string | undefined;
|
|
96
|
-
cpu_request?: string | undefined;
|
|
97
|
-
cpu_limit?: string | undefined;
|
|
98
|
-
}>;
|
|
99
|
-
export declare function getLogs(input: z.infer<typeof kubectlGetLogsSchema>, context?: {
|
|
100
|
-
context?: string;
|
|
101
|
-
}): Promise<string>;
|
|
102
|
-
export declare function describeResource(input: z.infer<typeof kubectlDescribeSchema>, context?: {
|
|
103
|
-
context?: string;
|
|
104
|
-
}): Promise<string>;
|
|
105
|
-
export declare function restartPod(input: z.infer<typeof restartPodSchema>, context?: {
|
|
106
|
-
context?: string;
|
|
107
|
-
}): Promise<string>;
|
|
108
|
-
export declare function rolloutRestart(input: z.infer<typeof rolloutRestartSchema>, context?: {
|
|
109
|
-
context?: string;
|
|
110
|
-
}): Promise<string>;
|
|
111
|
-
export declare function scaleDeployment(input: z.infer<typeof scaleDeploymentSchema>, context?: {
|
|
112
|
-
context?: string;
|
|
113
|
-
}): Promise<string>;
|
|
114
|
-
export declare function getEvents(input: z.infer<typeof getEventsSchema>, context?: {
|
|
115
|
-
context?: string;
|
|
116
|
-
}): Promise<string>;
|
|
117
|
-
export declare function setResources(input: z.infer<typeof setResourcesSchema>, context?: {
|
|
118
|
-
context?: string;
|
|
119
|
-
}): Promise<string>;
|
package/dist/kb/loader.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export declare function buildSystemPrompt(kbDir: string): string;
|
package/dist/kb/writer.d.ts
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
export declare function ensureKbDir(kbDir: string): void;
|
|
2
|
-
export declare function writeClusterKb(kbDir: string, content: string): void;
|
|
3
|
-
export declare function writeApplicationsKb(kbDir: string, content: string): void;
|
|
4
|
-
export declare function writeProjectKb(kbDir: string, projectName: string, content: string): void;
|
|
5
|
-
export declare function writeRunbook(kbDir: string, name: string, content: string): void;
|
|
6
|
-
/**
|
|
7
|
-
* Write a gave-up issue to the unresolved/ directory.
|
|
8
|
-
* Files are named by issue key so repeated give-ups overwrite rather than accumulate.
|
|
9
|
-
* The agent reads these as KB context on future runs.
|
|
10
|
-
*/
|
|
11
|
-
export declare function writeUnresolved(kbDir: string, issueKey: string, content: string): void;
|
|
12
|
-
export declare function writeIncident(kbDir: string, date: string, name: string, content: string): void;
|
package/dist/kubectl-config.d.ts
DELETED
package/dist/kubectl.d.ts
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
export declare class KubectlError extends Error {
|
|
2
|
-
readonly stderr?: string | undefined;
|
|
3
|
-
readonly exitCode?: number | undefined;
|
|
4
|
-
constructor(message: string, stderr?: string | undefined, exitCode?: number | undefined);
|
|
5
|
-
}
|
|
6
|
-
export declare function parseKubectlJson(raw: string): unknown;
|
|
7
|
-
export interface KubectlOptions {
|
|
8
|
-
context?: string;
|
|
9
|
-
namespace?: string;
|
|
10
|
-
timeout?: number;
|
|
11
|
-
}
|
|
12
|
-
export declare function kubectl(args: string[], options?: KubectlOptions): Promise<string>;
|
|
13
|
-
export declare function kubectlJson(args: string[], options?: KubectlOptions): Promise<unknown>;
|
package/dist/monitor/checks.d.ts
DELETED
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
import type { Issue } from "./types.js";
|
|
2
|
-
interface PodStatus {
|
|
3
|
-
metadata: {
|
|
4
|
-
name: string;
|
|
5
|
-
namespace: string;
|
|
6
|
-
};
|
|
7
|
-
status: {
|
|
8
|
-
phase: string;
|
|
9
|
-
reason?: string;
|
|
10
|
-
containerStatuses?: Array<{
|
|
11
|
-
name: string;
|
|
12
|
-
state: {
|
|
13
|
-
waiting?: {
|
|
14
|
-
reason?: string;
|
|
15
|
-
};
|
|
16
|
-
terminated?: {
|
|
17
|
-
reason?: string;
|
|
18
|
-
};
|
|
19
|
-
running?: Record<string, unknown>;
|
|
20
|
-
};
|
|
21
|
-
lastState?: {
|
|
22
|
-
terminated?: {
|
|
23
|
-
reason?: string;
|
|
24
|
-
};
|
|
25
|
-
};
|
|
26
|
-
restartCount: number;
|
|
27
|
-
}>;
|
|
28
|
-
};
|
|
29
|
-
}
|
|
30
|
-
interface NodeStatus {
|
|
31
|
-
metadata: {
|
|
32
|
-
name: string;
|
|
33
|
-
};
|
|
34
|
-
status: {
|
|
35
|
-
conditions: Array<{
|
|
36
|
-
type: string;
|
|
37
|
-
status: string;
|
|
38
|
-
reason?: string;
|
|
39
|
-
}>;
|
|
40
|
-
};
|
|
41
|
-
}
|
|
42
|
-
export declare function findPodIssues(podList: {
|
|
43
|
-
items: PodStatus[];
|
|
44
|
-
}): Issue[];
|
|
45
|
-
interface JobStatus {
|
|
46
|
-
metadata: {
|
|
47
|
-
name: string;
|
|
48
|
-
namespace: string;
|
|
49
|
-
};
|
|
50
|
-
status: {
|
|
51
|
-
failed?: number;
|
|
52
|
-
succeeded?: number;
|
|
53
|
-
active?: number;
|
|
54
|
-
conditions?: Array<{
|
|
55
|
-
type: string;
|
|
56
|
-
status: string;
|
|
57
|
-
reason?: string;
|
|
58
|
-
message?: string;
|
|
59
|
-
}>;
|
|
60
|
-
};
|
|
61
|
-
spec?: {
|
|
62
|
-
backoffLimit?: number;
|
|
63
|
-
};
|
|
64
|
-
}
|
|
65
|
-
export declare function findJobIssues(jobList: {
|
|
66
|
-
items: JobStatus[];
|
|
67
|
-
}): Issue[];
|
|
68
|
-
export declare function findNodeIssues(nodeList: {
|
|
69
|
-
items: NodeStatus[];
|
|
70
|
-
}): Issue[];
|
|
71
|
-
export {};
|
package/dist/monitor/index.d.ts
DELETED
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
import { type KubectlOptions } from "../kubectl.js";
|
|
2
|
-
import type { Issue } from "./types.js";
|
|
3
|
-
export type IssueCallback = (issues: Issue[]) => void | Promise<void>;
|
|
4
|
-
export type ResolveCallback = (resolvedKeys: string[]) => void | Promise<void>;
|
|
5
|
-
export interface CheckSummary {
|
|
6
|
-
issues: Issue[];
|
|
7
|
-
podCount: number;
|
|
8
|
-
nodeCount: number;
|
|
9
|
-
namespaceCount: number;
|
|
10
|
-
}
|
|
11
|
-
export interface RunChecksScope {
|
|
12
|
-
/**
|
|
13
|
-
* Namespaces to scope the scan to. When set, kubectl queries are issued
|
|
14
|
-
* per-namespace (e.g. `kubectl get pods -n foo`) instead of the
|
|
15
|
-
* cluster-wide `--all-namespaces` form. Cluster-scoped queries
|
|
16
|
-
* (`get nodes`, `get namespaces`) are still attempted but their failures
|
|
17
|
-
* are tolerated so namespace-scoped service accounts can use `kubeagent check`.
|
|
18
|
-
*/
|
|
19
|
-
namespaces?: string[];
|
|
20
|
-
}
|
|
21
|
-
export declare function runChecks(options: KubectlOptions): Promise<Issue[]>;
|
|
22
|
-
export declare function runChecks(options: KubectlOptions, withSummary: true, scope?: RunChecksScope): Promise<CheckSummary>;
|
|
23
|
-
export declare function computeResolvedKeys(activeKeys: Set<string>, currentKeys: Set<string>): string[];
|
|
24
|
-
export declare function updateActiveKeys(activeKeys: Set<string>, resolvedKeys: string[], currentKeys: Set<string>): void;
|
|
25
|
-
export declare function startMonitor(options: KubectlOptions, intervalMs: number, onIssues: IssueCallback, onResolved?: ResolveCallback, onFirstCycle?: () => void): {
|
|
26
|
-
stop: () => void;
|
|
27
|
-
};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from "vitest";
|
|
2
|
-
import { computeResolvedKeys, updateActiveKeys } from "./index.js";
|
|
3
|
-
describe("resolve tracking", () => {
|
|
4
|
-
it("returns keys that disappeared since last tick", () => {
|
|
5
|
-
const active = new Set(["pod_crashloop:prod:web", "pod_oom:prod:worker"]);
|
|
6
|
-
const current = new Set(["pod_oom:prod:worker"]);
|
|
7
|
-
expect(computeResolvedKeys(active, current)).toEqual(["pod_crashloop:prod:web"]);
|
|
8
|
-
});
|
|
9
|
-
it("returns empty array when nothing cleared", () => {
|
|
10
|
-
const active = new Set(["pod_crashloop:prod:web"]);
|
|
11
|
-
const current = new Set(["pod_crashloop:prod:web"]);
|
|
12
|
-
expect(computeResolvedKeys(active, current)).toEqual([]);
|
|
13
|
-
});
|
|
14
|
-
it("updates active set correctly after resolve", () => {
|
|
15
|
-
const active = new Set(["pod_crashloop:prod:web", "pod_oom:prod:worker"]);
|
|
16
|
-
const current = new Set(["pod_oom:prod:worker", "node_not_ready::node1"]);
|
|
17
|
-
const resolved = computeResolvedKeys(active, current);
|
|
18
|
-
updateActiveKeys(active, resolved, current);
|
|
19
|
-
expect([...active].sort()).toEqual(["node_not_ready::node1", "pod_oom:prod:worker"]);
|
|
20
|
-
});
|
|
21
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,87 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
2
|
-
import { runChecks } from "./index.js";
|
|
3
|
-
import * as kubectl from "../kubectl.js";
|
|
4
|
-
describe("runChecks namespace scope", () => {
|
|
5
|
-
beforeEach(() => {
|
|
6
|
-
vi.restoreAllMocks();
|
|
7
|
-
});
|
|
8
|
-
it("queries per-namespace when scope.namespaces is set", async () => {
|
|
9
|
-
const calls = [];
|
|
10
|
-
vi.spyOn(kubectl, "kubectlJson").mockImplementation(async (args) => {
|
|
11
|
-
calls.push(args);
|
|
12
|
-
return { items: [] };
|
|
13
|
-
});
|
|
14
|
-
await runChecks({}, true, { namespaces: ["foo", "bar"] });
|
|
15
|
-
// Pods: one per requested namespace, never --all-namespaces
|
|
16
|
-
const podCalls = calls.filter((c) => c[0] === "get" && c[1] === "pods");
|
|
17
|
-
expect(podCalls).toHaveLength(2);
|
|
18
|
-
expect(podCalls[0]).toEqual(["get", "pods", "-n", "foo"]);
|
|
19
|
-
expect(podCalls[1]).toEqual(["get", "pods", "-n", "bar"]);
|
|
20
|
-
expect(calls.some((c) => c.includes("--all-namespaces") && c[1] === "pods")).toBe(false);
|
|
21
|
-
// Jobs: same per-namespace pattern
|
|
22
|
-
const jobCalls = calls.filter((c) => c[0] === "get" && c[1] === "jobs");
|
|
23
|
-
expect(jobCalls).toHaveLength(2);
|
|
24
|
-
expect(jobCalls[0]).toContain("foo");
|
|
25
|
-
expect(jobCalls[1]).toContain("bar");
|
|
26
|
-
expect(calls.some((c) => c.includes("--all-namespaces") && c[1] === "jobs")).toBe(false);
|
|
27
|
-
});
|
|
28
|
-
it("uses --all-namespaces when no scope is given", async () => {
|
|
29
|
-
const calls = [];
|
|
30
|
-
vi.spyOn(kubectl, "kubectlJson").mockImplementation(async (args) => {
|
|
31
|
-
calls.push(args);
|
|
32
|
-
return { items: [] };
|
|
33
|
-
});
|
|
34
|
-
await runChecks({});
|
|
35
|
-
expect(calls.some((c) => c[0] === "get" && c[1] === "pods" && c.includes("--all-namespaces"))).toBe(true);
|
|
36
|
-
expect(calls.some((c) => c[0] === "get" && c[1] === "jobs" && c.includes("--all-namespaces"))).toBe(true);
|
|
37
|
-
});
|
|
38
|
-
it("tolerates forbidden errors on cluster-scoped node queries", async () => {
|
|
39
|
-
// Simulate a namespace-scoped service account: pods succeed, nodes 403
|
|
40
|
-
vi.spyOn(kubectl, "kubectlJson").mockImplementation(async (args) => {
|
|
41
|
-
if (args[1] === "nodes") {
|
|
42
|
-
throw new Error("Error from server (Forbidden): nodes is forbidden");
|
|
43
|
-
}
|
|
44
|
-
return { items: [] };
|
|
45
|
-
});
|
|
46
|
-
const result = await runChecks({}, true, { namespaces: ["foo"] });
|
|
47
|
-
expect(result.issues).toEqual([]);
|
|
48
|
-
expect(result.nodeCount).toBe(0);
|
|
49
|
-
});
|
|
50
|
-
it("reports namespaceCount = scope length when scoped (skips cluster ns list)", async () => {
|
|
51
|
-
const calls = [];
|
|
52
|
-
vi.spyOn(kubectl, "kubectlJson").mockImplementation(async (args) => {
|
|
53
|
-
calls.push(args);
|
|
54
|
-
return { items: [] };
|
|
55
|
-
});
|
|
56
|
-
const result = await runChecks({}, true, { namespaces: ["foo", "bar", "baz"] });
|
|
57
|
-
expect(result.namespaceCount).toBe(3);
|
|
58
|
-
// Should not have called get namespaces — that's cluster-scoped and may 403.
|
|
59
|
-
expect(calls.some((c) => c[0] === "get" && c[1] === "namespaces")).toBe(false);
|
|
60
|
-
});
|
|
61
|
-
it("accumulates pod counts across namespaces", async () => {
|
|
62
|
-
const okPod = (ns, name) => ({
|
|
63
|
-
metadata: { name, namespace: ns },
|
|
64
|
-
status: { phase: "Running", containerStatuses: [] },
|
|
65
|
-
});
|
|
66
|
-
vi.spyOn(kubectl, "kubectlJson").mockImplementation(async (args) => {
|
|
67
|
-
if (args[1] === "pods" && args[3] === "foo") {
|
|
68
|
-
return { items: [okPod("foo", "a"), okPod("foo", "b"), okPod("foo", "c")] };
|
|
69
|
-
}
|
|
70
|
-
if (args[1] === "pods" && args[3] === "bar") {
|
|
71
|
-
return { items: [okPod("bar", "x"), okPod("bar", "y")] };
|
|
72
|
-
}
|
|
73
|
-
return { items: [] };
|
|
74
|
-
});
|
|
75
|
-
const result = await runChecks({}, true, { namespaces: ["foo", "bar"] });
|
|
76
|
-
expect(result.podCount).toBe(5);
|
|
77
|
-
});
|
|
78
|
-
it("empty scope.namespaces is treated as no scope (all-namespaces)", async () => {
|
|
79
|
-
const calls = [];
|
|
80
|
-
vi.spyOn(kubectl, "kubectlJson").mockImplementation(async (args) => {
|
|
81
|
-
calls.push(args);
|
|
82
|
-
return { items: [] };
|
|
83
|
-
});
|
|
84
|
-
await runChecks({}, true, { namespaces: [] });
|
|
85
|
-
expect(calls.some((c) => c[0] === "get" && c[1] === "pods" && c.includes("--all-namespaces"))).toBe(true);
|
|
86
|
-
});
|
|
87
|
-
});
|
package/dist/monitor/types.d.ts
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
export type Severity = "info" | "warning" | "critical";
|
|
2
|
-
export type IssueKind = "pod_crashloop" | "pod_oom" | "pod_image_pull" | "pod_pending" | "pod_failed" | "pod_evicted" | "pod_error" | "node_not_ready" | "node_pressure" | "cert_expiring" | "resource_high" | "job_failed" | "rollout_stuck";
|
|
3
|
-
export interface Issue {
|
|
4
|
-
kind: IssueKind;
|
|
5
|
-
severity: Severity;
|
|
6
|
-
namespace: string;
|
|
7
|
-
resource: string;
|
|
8
|
-
message: string;
|
|
9
|
-
details: Record<string, unknown>;
|
|
10
|
-
timestamp: Date;
|
|
11
|
-
}
|
package/dist/notify/discord.d.ts
DELETED
|
@@ -1,4 +0,0 @@
|
|
|
1
|
-
import type { Issue } from "../monitor/types.js";
|
|
2
|
-
import type { DiscordChannel } from "../config.js";
|
|
3
|
-
export declare function sendDiscordQuestion(channel: DiscordChannel, question: string, choices: string[] | undefined, clusterContext?: string): Promise<void>;
|
|
4
|
-
export declare function sendDiscord(issues: Issue[], channel: DiscordChannel, clusterContext?: string): Promise<void>;
|
package/dist/notify/index.d.ts
DELETED
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
import type { Issue } from "../monitor/types.js";
|
|
2
|
-
import type { KubeAgentConfig, NotificationChannel } from "../config.js";
|
|
3
|
-
export declare function sendNotification(issues: Issue[], config: KubeAgentConfig, clusterContext?: string): Promise<void>;
|
|
4
|
-
export declare function broadcastQuestion(question: string, choices: string[] | undefined, config: KubeAgentConfig, clusterContext?: string): Promise<void>;
|
|
5
|
-
export declare function sendResolve(resolvedKeys: string[], config: KubeAgentConfig, clusterContext?: string): Promise<void>;
|
|
6
|
-
export declare function describeChannel(channel: NotificationChannel): string;
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import type { Issue } from "../monitor/types.js";
|
|
2
|
-
import type { PagerDutyChannel } from "../config.js";
|
|
3
|
-
export declare function dedupKey(issue: Issue): string;
|
|
4
|
-
export declare function sendPagerDuty(issues: Issue[], channel: PagerDutyChannel, clusterContext?: string): Promise<void>;
|
|
5
|
-
export declare function resolvePagerDuty(dedupKeys: string[], channel: PagerDutyChannel): Promise<void>;
|
|
6
|
-
export declare function testPagerDutyCredentials(routingKey: string): Promise<{
|
|
7
|
-
ok: boolean;
|
|
8
|
-
error?: string;
|
|
9
|
-
}>;
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,97 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
2
|
-
const mockFetch = vi.fn();
|
|
3
|
-
vi.stubGlobal("fetch", mockFetch);
|
|
4
|
-
const channel = { type: "pagerduty", routing_key: "R0123456789abcdef", severity: "warning" };
|
|
5
|
-
const issue = { kind: "pod_crashloop", severity: "critical", namespace: "prod", resource: "api-web", message: "Pod crash-looping (3 times)", details: {}, timestamp: new Date("2026-01-01T00:00:00Z") };
|
|
6
|
-
describe("sendPagerDuty", () => {
|
|
7
|
-
beforeEach(() => {
|
|
8
|
-
mockFetch.mockReset();
|
|
9
|
-
mockFetch.mockResolvedValue({ ok: true, status: 202, json: async () => ({ status: "success" }) });
|
|
10
|
-
});
|
|
11
|
-
it("POSTs to PagerDuty Events API v2", async () => {
|
|
12
|
-
const { sendPagerDuty } = await import("./pagerduty.js");
|
|
13
|
-
await sendPagerDuty([issue], channel, "hetzner-prod");
|
|
14
|
-
expect(mockFetch.mock.calls[0][0]).toBe("https://events.pagerduty.com/v2/enqueue");
|
|
15
|
-
});
|
|
16
|
-
it("sends trigger with correct dedup_key", async () => {
|
|
17
|
-
const { sendPagerDuty } = await import("./pagerduty.js");
|
|
18
|
-
await sendPagerDuty([issue], channel);
|
|
19
|
-
const body = JSON.parse(mockFetch.mock.calls[0][1].body);
|
|
20
|
-
expect(body.event_action).toBe("trigger");
|
|
21
|
-
expect(body.dedup_key).toBe("kubeagent:pod_crashloop:prod:api-web");
|
|
22
|
-
expect(body.routing_key).toBe("R0123456789abcdef");
|
|
23
|
-
});
|
|
24
|
-
it("maps critical severity correctly", async () => {
|
|
25
|
-
const { sendPagerDuty } = await import("./pagerduty.js");
|
|
26
|
-
await sendPagerDuty([{ ...issue, severity: "critical" }], channel);
|
|
27
|
-
const body = JSON.parse(mockFetch.mock.calls[0][1].body);
|
|
28
|
-
expect(body.payload.severity).toBe("critical");
|
|
29
|
-
});
|
|
30
|
-
it("maps warning severity correctly", async () => {
|
|
31
|
-
const { sendPagerDuty } = await import("./pagerduty.js");
|
|
32
|
-
await sendPagerDuty([{ ...issue, severity: "warning" }], channel);
|
|
33
|
-
const body = JSON.parse(mockFetch.mock.calls[0][1].body);
|
|
34
|
-
expect(body.payload.severity).toBe("warning");
|
|
35
|
-
});
|
|
36
|
-
it("maps info severity correctly", async () => {
|
|
37
|
-
const { sendPagerDuty } = await import("./pagerduty.js");
|
|
38
|
-
await sendPagerDuty([{ ...issue, severity: "info" }], channel);
|
|
39
|
-
const body = JSON.parse(mockFetch.mock.calls[0][1].body);
|
|
40
|
-
expect(body.payload.severity).toBe("info");
|
|
41
|
-
});
|
|
42
|
-
it("fires one request per issue", async () => {
|
|
43
|
-
const { sendPagerDuty } = await import("./pagerduty.js");
|
|
44
|
-
await sendPagerDuty([issue, { ...issue, kind: "pod_oom", resource: "worker" }], channel);
|
|
45
|
-
expect(mockFetch).toHaveBeenCalledTimes(2);
|
|
46
|
-
});
|
|
47
|
-
it("does not throw on non-2xx", async () => {
|
|
48
|
-
mockFetch.mockResolvedValue({ ok: false, status: 400, statusText: "Bad Request", json: async () => ({}) });
|
|
49
|
-
const { sendPagerDuty } = await import("./pagerduty.js");
|
|
50
|
-
await expect(sendPagerDuty([issue], channel)).resolves.not.toThrow();
|
|
51
|
-
});
|
|
52
|
-
});
|
|
53
|
-
describe("resolvePagerDuty", () => {
|
|
54
|
-
beforeEach(() => {
|
|
55
|
-
mockFetch.mockReset();
|
|
56
|
-
mockFetch.mockResolvedValue({ ok: true, status: 202, json: async () => ({}) });
|
|
57
|
-
});
|
|
58
|
-
it("sends resolve events for each dedup key", async () => {
|
|
59
|
-
const { resolvePagerDuty } = await import("./pagerduty.js");
|
|
60
|
-
await resolvePagerDuty(["kubeagent:pod_crashloop:prod:api-web", "kubeagent:pod_oom:prod:worker"], channel);
|
|
61
|
-
expect(mockFetch).toHaveBeenCalledTimes(2);
|
|
62
|
-
const body0 = JSON.parse(mockFetch.mock.calls[0][1].body);
|
|
63
|
-
expect(body0.event_action).toBe("resolve");
|
|
64
|
-
expect(body0.dedup_key).toBe("kubeagent:pod_crashloop:prod:api-web");
|
|
65
|
-
});
|
|
66
|
-
it("does not throw on error", async () => {
|
|
67
|
-
mockFetch.mockRejectedValue(new Error("network failure"));
|
|
68
|
-
const { resolvePagerDuty } = await import("./pagerduty.js");
|
|
69
|
-
await expect(resolvePagerDuty(["kubeagent:pod_crashloop:prod:api-web"], channel)).resolves.not.toThrow();
|
|
70
|
-
});
|
|
71
|
-
});
|
|
72
|
-
describe("testPagerDutyCredentials", () => {
|
|
73
|
-
beforeEach(() => {
|
|
74
|
-
mockFetch.mockReset();
|
|
75
|
-
mockFetch.mockResolvedValue({ ok: true, status: 202, json: async () => ({}) });
|
|
76
|
-
});
|
|
77
|
-
it("returns ok:true when trigger succeeds", async () => {
|
|
78
|
-
const { testPagerDutyCredentials } = await import("./pagerduty.js");
|
|
79
|
-
const result = await testPagerDutyCredentials("R0123456789abcdef");
|
|
80
|
-
expect(result.ok).toBe(true);
|
|
81
|
-
expect(result.error).toBeUndefined();
|
|
82
|
-
});
|
|
83
|
-
it("returns ok:false with error when trigger fails", async () => {
|
|
84
|
-
mockFetch.mockResolvedValue({ ok: false, status: 400, statusText: "Bad Request", json: async () => ({ message: "Invalid routing key" }) });
|
|
85
|
-
const { testPagerDutyCredentials } = await import("./pagerduty.js");
|
|
86
|
-
const result = await testPagerDutyCredentials("bad-key");
|
|
87
|
-
expect(result.ok).toBe(false);
|
|
88
|
-
expect(result.error).toBe("Invalid routing key");
|
|
89
|
-
});
|
|
90
|
-
it("returns ok:false on network error", async () => {
|
|
91
|
-
mockFetch.mockRejectedValue(new Error("ECONNREFUSED"));
|
|
92
|
-
const { testPagerDutyCredentials } = await import("./pagerduty.js");
|
|
93
|
-
const result = await testPagerDutyCredentials("R0123456789abcdef");
|
|
94
|
-
expect(result.ok).toBe(false);
|
|
95
|
-
expect(result.error).toBe("ECONNREFUSED");
|
|
96
|
-
});
|
|
97
|
-
});
|
package/dist/notify/setup.d.ts
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import type { NotificationChannel, SlackChannel, TelegramChannel } from "../config.js";
|
|
2
|
-
import type { WebhookChannel, PagerDutyChannel, DiscordChannel, TeamsChannel } from "../config.js";
|
|
3
|
-
export declare function setupSlack(): Promise<SlackChannel | null>;
|
|
4
|
-
export declare function setupTelegram(): Promise<TelegramChannel | null>;
|
|
5
|
-
export declare function setupWebhook(): Promise<WebhookChannel | null>;
|
|
6
|
-
export declare function setupPagerDuty(): Promise<PagerDutyChannel | null>;
|
|
7
|
-
export declare function setupDiscord(): Promise<DiscordChannel | null>;
|
|
8
|
-
export declare function setupTeams(): Promise<TeamsChannel | null>;
|
|
9
|
-
export declare function interactiveAddChannel(): Promise<NotificationChannel | null>;
|
package/dist/notify/slack.d.ts
DELETED
|
@@ -1,4 +0,0 @@
|
|
|
1
|
-
import type { Issue } from "../monitor/types.js";
|
|
2
|
-
import type { SlackChannel } from "../config.js";
|
|
3
|
-
export declare function sendSlackQuestion(channel: SlackChannel, question: string, choices: string[] | undefined, clusterContext?: string): Promise<void>;
|
|
4
|
-
export declare function sendSlack(issues: Issue[], channel: SlackChannel, clusterContext?: string): Promise<void>;
|
package/dist/notify/teams.d.ts
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import type { Issue } from "../monitor/types.js";
|
|
2
|
-
import type { TelegramChannel } from "../config.js";
|
|
3
|
-
export declare function sendTelegramQuestion(channel: TelegramChannel, question: string, choices: string[] | undefined, clusterContext?: string): Promise<void>;
|
|
4
|
-
export declare function sendTelegram(issues: Issue[], channel: TelegramChannel, clusterContext?: string): Promise<void>;
|
|
5
|
-
export declare function autoDetectTelegramChatId(botToken: string): Promise<string | null>;
|
|
6
|
-
export declare function testTelegramCredentials(botToken: string, chatId: string): Promise<{
|
|
7
|
-
ok: boolean;
|
|
8
|
-
error?: string;
|
|
9
|
-
}>;
|
package/dist/notify/webhook.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
2
|
-
const mockFetch = vi.fn();
|
|
3
|
-
vi.stubGlobal("fetch", mockFetch);
|
|
4
|
-
// Mock DNS so the SSRF guard resolves example.com to a public address without
|
|
5
|
-
// real network access, keeping tests hermetic.
|
|
6
|
-
const mockLookup = vi.fn();
|
|
7
|
-
vi.mock("node:dns", () => ({
|
|
8
|
-
promises: { lookup: (...args) => mockLookup(...args) },
|
|
9
|
-
}));
|
|
10
|
-
const channel = { type: "webhook", url: "https://example.com/hook", secret: "testsecret", severity: "warning" };
|
|
11
|
-
const issue = { kind: "pod_crashloop", severity: "critical", namespace: "prod", resource: "api-web", message: "Pod crash-looping", details: { restartCount: 3 }, timestamp: new Date("2026-01-01T00:00:00Z") };
|
|
12
|
-
describe("sendWebhook", () => {
|
|
13
|
-
beforeEach(() => {
|
|
14
|
-
mockFetch.mockReset();
|
|
15
|
-
mockFetch.mockResolvedValue({ ok: true, status: 200, statusText: "OK" });
|
|
16
|
-
mockLookup.mockReset();
|
|
17
|
-
mockLookup.mockResolvedValue([{ address: "93.184.216.34", family: 4 }]);
|
|
18
|
-
});
|
|
19
|
-
it("POSTs to the configured URL", async () => {
|
|
20
|
-
const { sendWebhook } = await import("./webhook.js");
|
|
21
|
-
await sendWebhook([issue], channel, "hetzner-prod");
|
|
22
|
-
expect(mockFetch).toHaveBeenCalledOnce();
|
|
23
|
-
expect(mockFetch.mock.calls[0][0]).toBe("https://example.com/hook");
|
|
24
|
-
expect(mockFetch.mock.calls[0][1].method).toBe("POST");
|
|
25
|
-
});
|
|
26
|
-
it("sends Authorization: Bearer header when secret is set", async () => {
|
|
27
|
-
const { sendWebhook } = await import("./webhook.js");
|
|
28
|
-
await sendWebhook([issue], channel, "hetzner-prod");
|
|
29
|
-
expect(mockFetch.mock.calls[0][1].headers["Authorization"]).toBe("Bearer testsecret");
|
|
30
|
-
});
|
|
31
|
-
it("omits Authorization header when no secret", async () => {
|
|
32
|
-
const noSecretChannel = { ...channel, secret: undefined };
|
|
33
|
-
const { sendWebhook } = await import("./webhook.js");
|
|
34
|
-
await sendWebhook([issue], noSecretChannel);
|
|
35
|
-
expect(mockFetch.mock.calls[0][1].headers["Authorization"]).toBeUndefined();
|
|
36
|
-
});
|
|
37
|
-
it("includes cluster, timestamp, and issues in payload", async () => {
|
|
38
|
-
const { sendWebhook } = await import("./webhook.js");
|
|
39
|
-
await sendWebhook([issue], channel, "hetzner-prod");
|
|
40
|
-
const body = JSON.parse(mockFetch.mock.calls[0][1].body);
|
|
41
|
-
expect(body.cluster).toBe("hetzner-prod");
|
|
42
|
-
expect(body.issues).toHaveLength(1);
|
|
43
|
-
expect(body.issues[0].kind).toBe("pod_crashloop");
|
|
44
|
-
});
|
|
45
|
-
it("does not throw on non-2xx response", async () => {
|
|
46
|
-
mockFetch.mockResolvedValue({ ok: false, status: 500, statusText: "Error" });
|
|
47
|
-
const { sendWebhook } = await import("./webhook.js");
|
|
48
|
-
await expect(sendWebhook([issue], channel)).resolves.not.toThrow();
|
|
49
|
-
});
|
|
50
|
-
it("does not throw on network error", async () => {
|
|
51
|
-
mockFetch.mockRejectedValue(new Error("network failure"));
|
|
52
|
-
const { sendWebhook } = await import("./webhook.js");
|
|
53
|
-
await expect(sendWebhook([issue], channel)).resolves.not.toThrow();
|
|
54
|
-
});
|
|
55
|
-
it("rejects invalid URL without throwing", async () => {
|
|
56
|
-
const badChannel = { ...channel, url: "not-a-url" };
|
|
57
|
-
const { sendWebhook } = await import("./webhook.js");
|
|
58
|
-
await expect(sendWebhook([issue], badChannel)).resolves.not.toThrow();
|
|
59
|
-
expect(mockFetch).not.toHaveBeenCalled();
|
|
60
|
-
});
|
|
61
|
-
it("blocks SSRF: does not fetch a literal private/metadata IP", async () => {
|
|
62
|
-
const metaChannel = { ...channel, url: "http://169.254.169.254/latest/meta-data/" };
|
|
63
|
-
const { sendWebhook } = await import("./webhook.js");
|
|
64
|
-
await expect(sendWebhook([issue], metaChannel)).resolves.not.toThrow();
|
|
65
|
-
expect(mockFetch).not.toHaveBeenCalled();
|
|
66
|
-
});
|
|
67
|
-
it("blocks SSRF: does not fetch a hostname resolving to loopback", async () => {
|
|
68
|
-
mockLookup.mockResolvedValue([{ address: "127.0.0.1", family: 4 }]);
|
|
69
|
-
const rebindChannel = { ...channel, url: "https://internal.evil.example/hook" };
|
|
70
|
-
const { sendWebhook } = await import("./webhook.js");
|
|
71
|
-
await expect(sendWebhook([issue], rebindChannel)).resolves.not.toThrow();
|
|
72
|
-
expect(mockFetch).not.toHaveBeenCalled();
|
|
73
|
-
});
|
|
74
|
-
});
|