kubeagent 0.1.10 → 0.1.11
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/dist/config.d.ts +13 -1
- package/dist/notify/discord.d.ts +4 -0
- package/dist/notify/discord.js +80 -0
- package/dist/notify/index.js +15 -0
- package/dist/notify/setup.d.ts +3 -1
- package/dist/notify/setup.js +50 -2
- package/dist/notify/teams.d.ts +3 -0
- package/dist/notify/teams.js +55 -0
- package/package.json +1 -1
package/dist/config.d.ts
CHANGED
|
@@ -38,7 +38,19 @@ export interface PagerDutyChannel {
|
|
|
38
38
|
severity: NotificationSeverity;
|
|
39
39
|
label?: string;
|
|
40
40
|
}
|
|
41
|
-
export
|
|
41
|
+
export interface DiscordChannel {
|
|
42
|
+
type: "discord";
|
|
43
|
+
webhook_url: string;
|
|
44
|
+
severity: NotificationSeverity;
|
|
45
|
+
label?: string;
|
|
46
|
+
}
|
|
47
|
+
export interface TeamsChannel {
|
|
48
|
+
type: "teams";
|
|
49
|
+
webhook_url: string;
|
|
50
|
+
severity: NotificationSeverity;
|
|
51
|
+
label?: string;
|
|
52
|
+
}
|
|
53
|
+
export type NotificationChannel = SlackChannel | TelegramChannel | WebhookChannel | PagerDutyChannel | DiscordChannel | TeamsChannel;
|
|
42
54
|
export declare const ALL_ACTIONS: readonly ["restart_pod", "rollout_restart", "scale_deployment", "set_resources"];
|
|
43
55
|
export type RemediationAction = (typeof ALL_ACTIONS)[number];
|
|
44
56
|
export interface KubeAgentConfig {
|
|
@@ -0,0 +1,4 @@
|
|
|
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>;
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
const CRITICAL_COLOR = 0xe74c3c; // red
|
|
2
|
+
const WARNING_COLOR = 0xf39c12; // yellow
|
|
3
|
+
const INFO_COLOR = 0x3498db; // blue
|
|
4
|
+
function formatDiscordPayload(issues, clusterContext) {
|
|
5
|
+
const critical = issues.filter((i) => i.severity === "critical");
|
|
6
|
+
const warning = issues.filter((i) => i.severity === "warning");
|
|
7
|
+
const summary = [
|
|
8
|
+
critical.length ? `${critical.length} critical` : "",
|
|
9
|
+
warning.length ? `${warning.length} warning` : "",
|
|
10
|
+
].filter(Boolean).join(", ");
|
|
11
|
+
const topSeverity = critical.length ? "critical" : warning.length ? "warning" : "info";
|
|
12
|
+
const color = topSeverity === "critical" ? CRITICAL_COLOR : topSeverity === "warning" ? WARNING_COLOR : INFO_COLOR;
|
|
13
|
+
const description = issues
|
|
14
|
+
.map((i) => {
|
|
15
|
+
const icon = i.severity === "critical" ? "🔴" : i.severity === "warning" ? "🟡" : "🔵";
|
|
16
|
+
const ns = i.namespace ? ` \`${i.namespace}\`` : "";
|
|
17
|
+
return `${icon} ${i.message}${ns}`;
|
|
18
|
+
})
|
|
19
|
+
.join("\n");
|
|
20
|
+
return {
|
|
21
|
+
embeds: [
|
|
22
|
+
{
|
|
23
|
+
title: "⚠ KubeAgent Alert",
|
|
24
|
+
description: clusterContext
|
|
25
|
+
? `**Cluster:** \`${clusterContext}\`\n${summary}\n\n${description}`
|
|
26
|
+
: `${summary}\n\n${description}`,
|
|
27
|
+
color,
|
|
28
|
+
timestamp: new Date().toISOString(),
|
|
29
|
+
footer: { text: "KubeAgent" },
|
|
30
|
+
},
|
|
31
|
+
],
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
async function postToDiscord(webhookUrl, payload) {
|
|
35
|
+
try {
|
|
36
|
+
const url = new URL(webhookUrl);
|
|
37
|
+
if (!["https:", "http:"].includes(url.protocol)) {
|
|
38
|
+
console.error("Discord: invalid URL protocol");
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
console.error("Discord: invalid webhook URL");
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
try {
|
|
47
|
+
const res = await fetch(webhookUrl, {
|
|
48
|
+
method: "POST",
|
|
49
|
+
headers: { "Content-Type": "application/json" },
|
|
50
|
+
body: JSON.stringify(payload),
|
|
51
|
+
signal: AbortSignal.timeout(10_000),
|
|
52
|
+
});
|
|
53
|
+
if (!res.ok)
|
|
54
|
+
console.error(`Discord webhook failed: ${res.status} ${res.statusText}`);
|
|
55
|
+
}
|
|
56
|
+
catch (err) {
|
|
57
|
+
console.error(`Discord webhook error: ${err.message}`);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
export async function sendDiscordQuestion(channel, question, choices, clusterContext) {
|
|
61
|
+
const ctx = clusterContext ? `\n**Cluster:** \`${clusterContext}\`` : "";
|
|
62
|
+
const choiceLines = choices?.map((c, i) => `${i + 1}. ${c}`) ?? [];
|
|
63
|
+
const body = choiceLines.length
|
|
64
|
+
? `${question}\n\n${choiceLines.join("\n")}\n\n*Answer via terminal or reply in your incident channel.*`
|
|
65
|
+
: `${question}\n\n*Answer required via terminal.*`;
|
|
66
|
+
await postToDiscord(channel.webhook_url, {
|
|
67
|
+
embeds: [
|
|
68
|
+
{
|
|
69
|
+
title: "❓ KubeAgent needs input",
|
|
70
|
+
description: `**Question**${ctx}\n\n${body}`,
|
|
71
|
+
color: INFO_COLOR,
|
|
72
|
+
timestamp: new Date().toISOString(),
|
|
73
|
+
footer: { text: "KubeAgent" },
|
|
74
|
+
},
|
|
75
|
+
],
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
export async function sendDiscord(issues, channel, clusterContext) {
|
|
79
|
+
await postToDiscord(channel.webhook_url, formatDiscordPayload(issues, clusterContext));
|
|
80
|
+
}
|
package/dist/notify/index.js
CHANGED
|
@@ -2,6 +2,8 @@ import { sendSlack, sendSlackQuestion } from "./slack.js";
|
|
|
2
2
|
import { sendTelegram, sendTelegramQuestion } from "./telegram.js";
|
|
3
3
|
import { sendWebhook } from "./webhook.js";
|
|
4
4
|
import { sendPagerDuty, resolvePagerDuty } from "./pagerduty.js";
|
|
5
|
+
import { sendDiscord, sendDiscordQuestion } from "./discord.js";
|
|
6
|
+
import { sendTeams } from "./teams.js";
|
|
5
7
|
const SEVERITY_ORDER = { info: 0, warning: 1, critical: 2 };
|
|
6
8
|
export async function sendNotification(issues, config, clusterContext) {
|
|
7
9
|
if (!config.notifications.channels.length)
|
|
@@ -24,6 +26,12 @@ export async function sendNotification(issues, config, clusterContext) {
|
|
|
24
26
|
case "pagerduty":
|
|
25
27
|
await sendPagerDuty(filtered, channel, clusterContext);
|
|
26
28
|
break;
|
|
29
|
+
case "discord":
|
|
30
|
+
await sendDiscord(filtered, channel, clusterContext);
|
|
31
|
+
break;
|
|
32
|
+
case "teams":
|
|
33
|
+
await sendTeams(filtered, channel, clusterContext);
|
|
34
|
+
break;
|
|
27
35
|
}
|
|
28
36
|
}));
|
|
29
37
|
}
|
|
@@ -36,7 +44,10 @@ export async function broadcastQuestion(question, choices, config, clusterContex
|
|
|
36
44
|
case "telegram": return sendTelegramQuestion(channel, question, choices, clusterContext);
|
|
37
45
|
case "webhook":
|
|
38
46
|
case "pagerduty":
|
|
47
|
+
case "teams":
|
|
39
48
|
return;
|
|
49
|
+
case "discord":
|
|
50
|
+
return sendDiscordQuestion(channel, question, choices, clusterContext);
|
|
40
51
|
}
|
|
41
52
|
}));
|
|
42
53
|
}
|
|
@@ -61,5 +72,9 @@ export function describeChannel(channel) {
|
|
|
61
72
|
return `Webhook${label} ${channel.url.slice(0, 40)}… min: ${channel.severity}`;
|
|
62
73
|
case "pagerduty":
|
|
63
74
|
return `PagerDuty${label} key: ${channel.routing_key.slice(0, 8)}… min: ${channel.severity}`;
|
|
75
|
+
case "discord":
|
|
76
|
+
return `Discord${label} ${channel.webhook_url.slice(0, 40)}… min: ${channel.severity}`;
|
|
77
|
+
case "teams":
|
|
78
|
+
return `Teams${label} ${channel.webhook_url.slice(0, 40)}… min: ${channel.severity}`;
|
|
64
79
|
}
|
|
65
80
|
}
|
package/dist/notify/setup.d.ts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import type { NotificationChannel, SlackChannel, TelegramChannel } from "../config.js";
|
|
2
|
-
import type { WebhookChannel, PagerDutyChannel } from "../config.js";
|
|
2
|
+
import type { WebhookChannel, PagerDutyChannel, DiscordChannel, TeamsChannel } from "../config.js";
|
|
3
3
|
export declare function setupSlack(): Promise<SlackChannel | null>;
|
|
4
4
|
export declare function setupTelegram(): Promise<TelegramChannel | null>;
|
|
5
5
|
export declare function setupWebhook(): Promise<WebhookChannel | null>;
|
|
6
6
|
export declare function setupPagerDuty(): Promise<PagerDutyChannel | null>;
|
|
7
|
+
export declare function setupDiscord(): Promise<DiscordChannel | null>;
|
|
8
|
+
export declare function setupTeams(): Promise<TeamsChannel | null>;
|
|
7
9
|
export declare function interactiveAddChannel(): Promise<NotificationChannel | null>;
|
package/dist/notify/setup.js
CHANGED
|
@@ -4,6 +4,8 @@ import { sendSlack } from "./slack.js";
|
|
|
4
4
|
import { testTelegramCredentials, autoDetectTelegramChatId } from "./telegram.js";
|
|
5
5
|
import { testPagerDutyCredentials } from "./pagerduty.js";
|
|
6
6
|
import { sendWebhook } from "./webhook.js";
|
|
7
|
+
import { sendDiscord } from "./discord.js";
|
|
8
|
+
import { sendTeams } from "./teams.js";
|
|
7
9
|
import { loadAuth } from "../auth.js";
|
|
8
10
|
import { registerWebhook } from "../proxy-client.js";
|
|
9
11
|
import { randomBytes } from "node:crypto";
|
|
@@ -151,18 +153,60 @@ export async function setupPagerDuty() {
|
|
|
151
153
|
}
|
|
152
154
|
return { type: "pagerduty", routing_key, severity, label: label || undefined };
|
|
153
155
|
}
|
|
156
|
+
export async function setupDiscord() {
|
|
157
|
+
console.log(chalk.bold("\n Discord Setup"));
|
|
158
|
+
console.log(chalk.dim(" Create an incoming webhook in your Discord server:"));
|
|
159
|
+
console.log(chalk.dim(" Server Settings → Integrations → Webhooks → New Webhook\n"));
|
|
160
|
+
const webhook_url = await ask("Webhook URL");
|
|
161
|
+
if (!webhook_url)
|
|
162
|
+
return null;
|
|
163
|
+
const label = await ask("Label (optional)", "e.g. #alerts");
|
|
164
|
+
const severity = await pickSeverity();
|
|
165
|
+
const channel = { type: "discord", webhook_url, severity, label: label || undefined };
|
|
166
|
+
process.stdout.write(chalk.dim("\n Sending test message..."));
|
|
167
|
+
await sendDiscord([{ kind: "pod_pending", severity: "warning", namespace: "test", resource: "kubeagent-test", message: "KubeAgent Discord integration working ✓", details: {}, timestamp: new Date() }], channel);
|
|
168
|
+
console.log(chalk.green(" sent!"));
|
|
169
|
+
const confirmed = await ask("Did you receive it? [y/N]");
|
|
170
|
+
if (confirmed.toLowerCase() !== "y") {
|
|
171
|
+
console.log(chalk.yellow(" Not confirmed — channel not saved."));
|
|
172
|
+
return null;
|
|
173
|
+
}
|
|
174
|
+
return channel;
|
|
175
|
+
}
|
|
176
|
+
export async function setupTeams() {
|
|
177
|
+
console.log(chalk.bold("\n Microsoft Teams Setup"));
|
|
178
|
+
console.log(chalk.dim(" Create an incoming webhook in your Teams channel:"));
|
|
179
|
+
console.log(chalk.dim(" Channel → ... → Connectors → Incoming Webhook → Configure\n"));
|
|
180
|
+
const webhook_url = await ask("Webhook URL");
|
|
181
|
+
if (!webhook_url)
|
|
182
|
+
return null;
|
|
183
|
+
const label = await ask("Label (optional)", "e.g. ops-alerts");
|
|
184
|
+
const severity = await pickSeverity();
|
|
185
|
+
const channel = { type: "teams", webhook_url, severity, label: label || undefined };
|
|
186
|
+
process.stdout.write(chalk.dim("\n Sending test message..."));
|
|
187
|
+
await sendTeams([{ kind: "pod_pending", severity: "warning", namespace: "test", resource: "kubeagent-test", message: "KubeAgent Teams integration working ✓", details: {}, timestamp: new Date() }], channel);
|
|
188
|
+
console.log(chalk.green(" sent!"));
|
|
189
|
+
const confirmed = await ask("Did you receive it? [y/N]");
|
|
190
|
+
if (confirmed.toLowerCase() !== "y") {
|
|
191
|
+
console.log(chalk.yellow(" Not confirmed — channel not saved."));
|
|
192
|
+
return null;
|
|
193
|
+
}
|
|
194
|
+
return channel;
|
|
195
|
+
}
|
|
154
196
|
export async function interactiveAddChannel() {
|
|
155
197
|
console.log(chalk.bold("\nAdd notification channel:\n"));
|
|
156
198
|
console.log(` ${chalk.cyan("1")}. Slack`);
|
|
157
199
|
console.log(` ${chalk.cyan("2")}. Telegram`);
|
|
158
200
|
console.log(` ${chalk.cyan("3")}. Webhook`);
|
|
159
201
|
console.log(` ${chalk.cyan("4")}. PagerDuty`);
|
|
160
|
-
console.log(` ${chalk.cyan("5")}.
|
|
202
|
+
console.log(` ${chalk.cyan("5")}. Discord`);
|
|
203
|
+
console.log(` ${chalk.cyan("6")}. Microsoft Teams`);
|
|
204
|
+
console.log(` ${chalk.cyan("7")}. Cancel\n`);
|
|
161
205
|
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
162
206
|
const choice = await new Promise((resolve) => {
|
|
163
207
|
rl.question(chalk.cyan(" Choice [1]: "), (a) => { rl.close(); resolve(a.trim()); });
|
|
164
208
|
});
|
|
165
|
-
if (choice === "
|
|
209
|
+
if (choice === "7" || choice === "")
|
|
166
210
|
return null;
|
|
167
211
|
if (choice === "2")
|
|
168
212
|
return setupTelegram();
|
|
@@ -170,5 +214,9 @@ export async function interactiveAddChannel() {
|
|
|
170
214
|
return setupWebhook();
|
|
171
215
|
if (choice === "4")
|
|
172
216
|
return setupPagerDuty();
|
|
217
|
+
if (choice === "5")
|
|
218
|
+
return setupDiscord();
|
|
219
|
+
if (choice === "6")
|
|
220
|
+
return setupTeams();
|
|
173
221
|
return setupSlack();
|
|
174
222
|
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
function formatTeamsPayload(issues, clusterContext) {
|
|
2
|
+
const critical = issues.filter((i) => i.severity === "critical");
|
|
3
|
+
const warning = issues.filter((i) => i.severity === "warning");
|
|
4
|
+
const summary = [
|
|
5
|
+
critical.length ? `${critical.length} critical` : "",
|
|
6
|
+
warning.length ? `${warning.length} warning` : "",
|
|
7
|
+
].filter(Boolean).join(", ");
|
|
8
|
+
const themeColor = critical.length ? "e74c3c" : warning.length ? "f39c12" : "3498db";
|
|
9
|
+
const facts = issues.map((i) => ({
|
|
10
|
+
name: i.severity === "critical" ? "🔴 Critical" : i.severity === "warning" ? "🟡 Warning" : "🔵 Info",
|
|
11
|
+
value: i.namespace ? `${i.message} \`${i.namespace}\`` : i.message,
|
|
12
|
+
}));
|
|
13
|
+
const sections = [];
|
|
14
|
+
if (clusterContext) {
|
|
15
|
+
sections.push({ facts: [{ name: "Cluster", value: `\`${clusterContext}\`` }] });
|
|
16
|
+
}
|
|
17
|
+
sections.push({ facts });
|
|
18
|
+
return {
|
|
19
|
+
"@type": "MessageCard",
|
|
20
|
+
"@context": "https://schema.org/extensions",
|
|
21
|
+
themeColor,
|
|
22
|
+
summary: `KubeAgent Alert — ${summary}`,
|
|
23
|
+
title: "⚠ KubeAgent Alert",
|
|
24
|
+
sections,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
async function postToTeams(webhookUrl, payload) {
|
|
28
|
+
try {
|
|
29
|
+
const url = new URL(webhookUrl);
|
|
30
|
+
if (!["https:", "http:"].includes(url.protocol)) {
|
|
31
|
+
console.error("Teams: invalid URL protocol");
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
console.error("Teams: invalid webhook URL");
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
try {
|
|
40
|
+
const res = await fetch(webhookUrl, {
|
|
41
|
+
method: "POST",
|
|
42
|
+
headers: { "Content-Type": "application/json" },
|
|
43
|
+
body: JSON.stringify(payload),
|
|
44
|
+
signal: AbortSignal.timeout(10_000),
|
|
45
|
+
});
|
|
46
|
+
if (!res.ok)
|
|
47
|
+
console.error(`Teams webhook failed: ${res.status} ${res.statusText}`);
|
|
48
|
+
}
|
|
49
|
+
catch (err) {
|
|
50
|
+
console.error(`Teams webhook error: ${err.message}`);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
export async function sendTeams(issues, channel, clusterContext) {
|
|
54
|
+
await postToTeams(channel.webhook_url, formatTeamsPayload(issues, clusterContext));
|
|
55
|
+
}
|