@intentius/chant-lexicon-prometheus 0.98.0 → 0.99.0
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/README.md +1 -1
- package/dist/import/embedded.d.ts +14 -0
- package/dist/import/embedded.d.ts.map +1 -0
- package/dist/import/generator.d.ts +15 -0
- package/dist/import/generator.d.ts.map +1 -1
- package/dist/import/parser.d.ts.map +1 -1
- package/dist/integrations.d.ts +531 -0
- package/dist/integrations.d.ts.map +1 -0
- package/dist/integrity.json +4 -4
- package/dist/lint/rules/literal-credential.d.ts.map +1 -1
- package/dist/manifest.json +1 -1
- package/dist/model.d.ts +58 -123
- package/dist/model.d.ts.map +1 -1
- package/dist/plugin.d.ts.map +1 -1
- package/dist/rules/literal-credential.ts +41 -10
- package/dist/skills/chant-prometheus-alertmanager.md +1 -1
- package/package.json +2 -2
- package/src/import/embedded.test.ts +56 -0
- package/src/import/embedded.ts +44 -0
- package/src/import/generated-types.e2e.test.ts +3 -1
- package/src/import/generator.test.ts +21 -4
- package/src/import/generator.ts +32 -24
- package/src/import/parser.test.ts +13 -0
- package/src/import/parser.ts +18 -1
- package/src/import/roundtrip.test.ts +46 -9
- package/src/import/testdata/alertmanager-full.yml +3 -3
- package/src/import/testdata/alertmanager-integrations.yml +215 -0
- package/src/import/testdata/fixtures.ts +8 -0
- package/src/import/testdata/upstream/alertmanager-conf-good.yml +120 -0
- package/src/import/testdata/upstream/alertmanager-mattermost-default-webhook-url-file.yml +16 -0
- package/src/import/testdata/upstream/alertmanager-opsgenie-default-apikey-file.yml +27 -0
- package/src/import/testdata/upstream/alertmanager-rocketchat-default-token-file.yml +25 -0
- package/src/import/testdata/upstream/alertmanager-sns-topic-arn.yml +17 -0
- package/src/import/testdata/upstream/alertmanager-telegram-default-bot-token-file.yml +16 -0
- package/src/import/testdata/upstream/alertmanager-victorops-default-apikey-file.yml +23 -0
- package/src/import/testdata/upstream/alertmanager-wechat-default-api-secret-file.yml +18 -0
- package/src/integrations.ts +538 -0
- package/src/lint/rules/literal-credential.ts +41 -10
- package/src/lint/rules/rules.test.ts +31 -1
- package/src/model.ts +136 -110
- package/src/plugin.ts +5 -0
- package/src/skills/chant-prometheus-alertmanager.md +1 -1
package/src/import/generator.ts
CHANGED
|
@@ -31,6 +31,8 @@ import type {
|
|
|
31
31
|
RuleGroupConfig,
|
|
32
32
|
TimeIntervalConfig,
|
|
33
33
|
} from "../model";
|
|
34
|
+
import { ALERTMANAGER_GLOBAL_FIELDS, RECEIVER_INTEGRATION_TYPES } from "../model";
|
|
35
|
+
import { PROMETHEUS_PIN } from "../pin";
|
|
34
36
|
import {
|
|
35
37
|
ALERTMANAGER_RESOURCE_TYPE,
|
|
36
38
|
RULE_FILE_RESOURCE_TYPE,
|
|
@@ -270,7 +272,24 @@ const LEXICON_NAMES = [
|
|
|
270
272
|
|
|
271
273
|
/** Generate the TypeScript declaring one rule file. */
|
|
272
274
|
export function generateRuleFileFiles(file: RuleFileConfig): GeneratedFile[] {
|
|
275
|
+
return generateRuleFile(file).files;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/** A rule group's declaration: the module and variable, and `rules` when it is an `Slo`'s group. */
|
|
279
|
+
export interface GroupDeclaration {
|
|
280
|
+
readonly path: string;
|
|
281
|
+
readonly name: string;
|
|
282
|
+
readonly member?: "rules";
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Generate the TypeScript declaring one rule file, and say where each of its
|
|
287
|
+
* groups is declared, in the file's order (#2962: a PrometheusRule's
|
|
288
|
+
* `spec.groups` refers to them).
|
|
289
|
+
*/
|
|
290
|
+
export function generateRuleFile(file: RuleFileConfig): { files: GeneratedFile[]; groups: GroupDeclaration[] } {
|
|
273
291
|
const names = new Names(LEXICON_NAMES);
|
|
292
|
+
const declared = new Map<RuleGroupConfig, GroupDeclaration>();
|
|
274
293
|
const slos: Array<{ group: RuleGroupConfig; props: NonNullable<ReturnType<typeof recognizeSlo>> }> = [];
|
|
275
294
|
const groups: RuleGroupConfig[] = [];
|
|
276
295
|
for (const g of file.groups) {
|
|
@@ -308,56 +327,45 @@ export function generateRuleFileFiles(file: RuleFileConfig): GeneratedFile[] {
|
|
|
308
327
|
lines.push(`${ctor}${objectOf(entries, 0, ctor.length)});`);
|
|
309
328
|
mod.block(lines);
|
|
310
329
|
mod.exports.push(v);
|
|
330
|
+
declared.set(g, { path: mod.path, name: v });
|
|
311
331
|
}
|
|
312
332
|
}
|
|
313
333
|
|
|
314
334
|
for (const [mod, items] of chunk(slos, "slos", "Service level objectives, each built by Slo() to its rule group")) {
|
|
315
335
|
modules.push(mod);
|
|
316
336
|
mod.use("Slo");
|
|
317
|
-
for (const { props } of items) {
|
|
337
|
+
for (const { group, props } of items) {
|
|
318
338
|
const v = sloVars[slos.findIndex((s) => s.props === props)];
|
|
319
339
|
const head = `const ${v} = Slo(`;
|
|
320
340
|
mod.block([`${head}${tsLiteral(props, 0, head.length)});`]);
|
|
321
341
|
mod.exports.push(v);
|
|
342
|
+
declared.set(group, { path: mod.path, name: v, member: "rules" });
|
|
322
343
|
}
|
|
323
344
|
}
|
|
324
345
|
|
|
325
|
-
return
|
|
346
|
+
return {
|
|
347
|
+
files: modules.map((m) => ({ path: m.path, content: m.render() })),
|
|
348
|
+
groups: file.groups.map((g) => declared.get(g)!),
|
|
349
|
+
};
|
|
326
350
|
}
|
|
327
351
|
|
|
328
352
|
// ── alertmanager.yml ─────────────────────────────────────────────────
|
|
329
353
|
|
|
330
354
|
/** Receiver fields the lexicon types, with the type each list is declared as. */
|
|
331
355
|
const RECEIVER_FIELD_TYPES: Record<string, string> = {
|
|
332
|
-
|
|
333
|
-
email_configs: "EmailConfig[]",
|
|
334
|
-
slack_configs: "SlackConfig[]",
|
|
335
|
-
pagerduty_configs: "PagerDutyConfig[]",
|
|
356
|
+
...Object.fromEntries(Object.entries(RECEIVER_INTEGRATION_TYPES).map(([k, t]) => [k, `${t}[]`])),
|
|
336
357
|
labels: "LabelSet",
|
|
337
358
|
};
|
|
338
359
|
|
|
339
360
|
/** `global:` fields the lexicon types. */
|
|
340
|
-
const GLOBAL_FIELDS = new Set(
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
"smtp_smarthost",
|
|
344
|
-
"smtp_hello",
|
|
345
|
-
"smtp_auth_username",
|
|
346
|
-
"smtp_auth_password",
|
|
347
|
-
"smtp_auth_password_file",
|
|
348
|
-
"smtp_auth_secret",
|
|
349
|
-
"smtp_auth_secret_file",
|
|
350
|
-
"smtp_auth_identity",
|
|
351
|
-
"smtp_require_tls",
|
|
352
|
-
"slack_api_url",
|
|
353
|
-
"slack_api_url_file",
|
|
354
|
-
"pagerduty_url",
|
|
355
|
-
"http_config",
|
|
356
|
-
]);
|
|
361
|
+
const GLOBAL_FIELDS = new Set<string>(ALERTMANAGER_GLOBAL_FIELDS);
|
|
362
|
+
|
|
363
|
+
const AM_VERSION = PROMETHEUS_PIN.alertmanager.version;
|
|
357
364
|
|
|
358
365
|
/** A comment naming the fields carried as data, wrapped at the line limit. */
|
|
359
366
|
function untypedComment(what: string, keys: string[]): string[] {
|
|
360
|
-
const
|
|
367
|
+
const one = keys.length === 1;
|
|
368
|
+
const text = `${what} ${keys.join(", ")} ${one ? "is not a field" : "are not fields"} Alertmanager ${AM_VERSION} defines, so ${one ? "it is" : "they are"} carried as data, untyped.`;
|
|
361
369
|
const lines: string[] = [];
|
|
362
370
|
let cur = "//";
|
|
363
371
|
for (const w of text.split(" ")) {
|
|
@@ -96,6 +96,19 @@ describe("parsePrometheusYaml", () => {
|
|
|
96
96
|
opsgenie_configs: [{ api_key: "literal" }],
|
|
97
97
|
},
|
|
98
98
|
]);
|
|
99
|
+
expect(parsed.warnings).toEqual([]);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
test("a receiver or global key Alertmanager doesn't define is carried and named in a warning", () => {
|
|
103
|
+
const parsed = parsePrometheusYaml(
|
|
104
|
+
lines("global: { resolve_timeout: 5m, pigeon_loft: roof }", "receivers:", " - name: chat", " pigeon_configs: [{ loft: roof }]"),
|
|
105
|
+
);
|
|
106
|
+
if (parsed.kind !== "alertmanager") throw new Error("expected alertmanager.yml");
|
|
107
|
+
expect(parsed.config.receivers).toEqual([{ name: "chat", pigeon_configs: [{ loft: "roof" }] }]);
|
|
108
|
+
expect(parsed.warnings).toEqual([
|
|
109
|
+
"global: pigeon_loft is not a global field in Alertmanager v0.34.1; carried as data, untyped",
|
|
110
|
+
'receiver "chat": pigeon_configs is not a receiver field in Alertmanager v0.34.1; carried as data, untyped',
|
|
111
|
+
]);
|
|
99
112
|
});
|
|
100
113
|
|
|
101
114
|
test("dates and times stay strings, as the Go loaders read them", () => {
|
package/src/import/parser.ts
CHANGED
|
@@ -21,6 +21,8 @@
|
|
|
21
21
|
import * as jsYaml from "js-yaml";
|
|
22
22
|
import type { TemplateIR, TemplateParser } from "@intentius/chant/import/parser";
|
|
23
23
|
import {
|
|
24
|
+
ALERTMANAGER_GLOBAL_FIELDS,
|
|
25
|
+
RECEIVER_INTEGRATIONS,
|
|
24
26
|
looksLikeAlertmanagerConfig,
|
|
25
27
|
looksLikeRuleFile,
|
|
26
28
|
type AlertmanagerConfig,
|
|
@@ -32,6 +34,7 @@ import {
|
|
|
32
34
|
type RuleGroupConfig,
|
|
33
35
|
type TimeIntervalConfig,
|
|
34
36
|
} from "../model";
|
|
37
|
+
import { PROMETHEUS_PIN } from "../pin";
|
|
35
38
|
|
|
36
39
|
/** The IR resource type for a whole rule file. */
|
|
37
40
|
export const RULE_FILE_RESOURCE_TYPE = "Prometheus::RuleFile";
|
|
@@ -264,12 +267,22 @@ function parseTimeIntervals(raw: unknown, at: string, warnings: string[]): TimeI
|
|
|
264
267
|
return out;
|
|
265
268
|
}
|
|
266
269
|
|
|
270
|
+
const AM_VERSION = PROMETHEUS_PIN.alertmanager.version;
|
|
271
|
+
const RECEIVER_KEYS = new Set<string>(["name", "labels", ...RECEIVER_INTEGRATIONS]);
|
|
272
|
+
const GLOBAL_KEYS = new Set<string>(ALERTMANAGER_GLOBAL_FIELDS);
|
|
273
|
+
|
|
267
274
|
function parseAlertmanager(doc: Record<string, unknown>, warnings: string[]): AlertmanagerConfig {
|
|
268
275
|
for (const k of Object.keys(doc)) {
|
|
269
276
|
if (!AM_SECTIONS.includes(k)) warnings.push(`top-level section "${k}" is not one chant declares; it is not carried`);
|
|
270
277
|
}
|
|
271
278
|
const config: AlertmanagerConfig = {};
|
|
272
|
-
if (isPlainObject(doc.global))
|
|
279
|
+
if (isPlainObject(doc.global)) {
|
|
280
|
+
config.global = doc.global as AlertmanagerConfig["global"];
|
|
281
|
+
const unknown = Object.keys(doc.global).filter((k) => !GLOBAL_KEYS.has(k));
|
|
282
|
+
if (unknown.length > 0) {
|
|
283
|
+
warnings.push(`global: ${unknown.join(", ")} ${unknown.length === 1 ? "is not a global field" : "are not global fields"} in Alertmanager ${AM_VERSION}; carried as data, untyped`);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
273
286
|
else if (doc.global !== undefined && doc.global !== null) warnings.push("global is not a mapping; it is not carried");
|
|
274
287
|
const templates = stringList(doc.templates, "templates", warnings);
|
|
275
288
|
if (templates) config.templates = templates;
|
|
@@ -288,6 +301,10 @@ function parseAlertmanager(doc: Record<string, unknown>, warnings: string[]): Al
|
|
|
288
301
|
warnings.push(`receivers[${i}] has no name; it is not carried`);
|
|
289
302
|
return;
|
|
290
303
|
}
|
|
304
|
+
const unknown = Object.keys(r).filter((k) => !RECEIVER_KEYS.has(k));
|
|
305
|
+
if (unknown.length > 0) {
|
|
306
|
+
warnings.push(`receiver "${String(r.name)}": ${unknown.join(", ")} ${unknown.length === 1 ? "is not a receiver field" : "are not receiver fields"} in Alertmanager ${AM_VERSION}; carried as data, untyped`);
|
|
307
|
+
}
|
|
291
308
|
receivers.push({ ...r, name: String(r.name) } as ReceiverConfig);
|
|
292
309
|
});
|
|
293
310
|
config.receivers = receivers;
|
|
@@ -24,7 +24,7 @@ import { build } from "@intentius/chant/build";
|
|
|
24
24
|
import { lintCommand } from "@intentius/chant/cli/commands/lint";
|
|
25
25
|
import { prometheusSerializer } from "../serializer";
|
|
26
26
|
import { amtoolCheckConfig, hasTool, promtoolCheckRules } from "../tools";
|
|
27
|
-
import type
|
|
27
|
+
import { RECEIVER_INTEGRATION_TYPES, type AlertmanagerConfig, type RouteConfig, type RuleFileConfig } from "../model";
|
|
28
28
|
import { PrometheusParser } from "./parser";
|
|
29
29
|
import { PrometheusGenerator } from "./generator";
|
|
30
30
|
import { builtFiles, exampleOutputs, pkgDir, read, sloOutputs, UPSTREAM, type BuiltFile } from "./testdata/fixtures";
|
|
@@ -256,10 +256,11 @@ describe("YAML -> TypeScript -> YAML", () => {
|
|
|
256
256
|
expect(out.source).toContain('url_file: "/etc/alertmanager/secrets/default-webhook-url"');
|
|
257
257
|
expect(out.source).toContain("description: '{{ template \"pagerduty.default.description\" . }}'");
|
|
258
258
|
expect(out.source).toContain('summary: "{{ .GroupLabels.alertname }} in {{ .GroupLabels.cluster }}"');
|
|
259
|
-
//
|
|
260
|
-
expect(out.source).toContain("
|
|
261
|
-
expect(out.source).toContain("
|
|
262
|
-
expect(out.source).toContain("
|
|
259
|
+
// Every integration and global field is typed.
|
|
260
|
+
expect(out.source).toContain("const oncallOpsgenie: OpsGenieConfig[] = [");
|
|
261
|
+
expect(out.source).toContain("const dbChatMsteams: MSTeamsConfig[] = [");
|
|
262
|
+
expect(out.source).toContain('opsgenie_api_key_file: "/etc/alertmanager/secrets/opsgenie-key"');
|
|
263
|
+
expect(out.source).not.toContain("Untyped");
|
|
263
264
|
// Deprecated spellings are rewritten, and each rewrite is named.
|
|
264
265
|
expect(out.source).toContain(`matchers: ['severity="page"', 'service=~"^(api|web)$"']`);
|
|
265
266
|
expect(out.source).toContain(`source_matchers: ['severity="page"']`);
|
|
@@ -270,20 +271,55 @@ describe("YAML -> TypeScript -> YAML", () => {
|
|
|
270
271
|
expect(out.warnings.join("\n")).toContain("top-level mute_time_intervals (weekends)");
|
|
271
272
|
});
|
|
272
273
|
|
|
274
|
+
/**
|
|
275
|
+
* What each upstream sample is expected to raise: literal credentials
|
|
276
|
+
* (Alertmanager's own examples write placeholder secrets inline), which
|
|
277
|
+
* PROM001 reports, and deprecated `match` spellings, which import warns
|
|
278
|
+
* about. A sample not listed raises nothing.
|
|
279
|
+
*/
|
|
280
|
+
const UPSTREAM_EXPECT: Record<string, { prom001?: number; warnings?: number }> = {
|
|
281
|
+
"alertmanager-simple.yml": { prom001: 4 },
|
|
282
|
+
// smtp_auth_password, slack_api_url, three routing_keys, the VictorOps and Opsgenie api_keys, Pushover's token and user_key.
|
|
283
|
+
"alertmanager-conf-good.yml": { prom001: 9, warnings: 9 },
|
|
284
|
+
"alertmanager-opsgenie-default-apikey-file.yml": { warnings: 1 },
|
|
285
|
+
"alertmanager-rocketchat-default-token-file.yml": { warnings: 1 },
|
|
286
|
+
"alertmanager-victorops-default-apikey-file.yml": { warnings: 1 },
|
|
287
|
+
"alertmanager-wechat-default-api-secret-file.yml": { prom001: 1 },
|
|
288
|
+
};
|
|
289
|
+
|
|
290
|
+
test("an alertmanager.yml using every receiver integration", async () => {
|
|
291
|
+
const out = await expectRoundTrip(read("alertmanager-integrations.yml"));
|
|
292
|
+
expect(out.warnings).toEqual([]);
|
|
293
|
+
expect(out.source).not.toContain("Untyped");
|
|
294
|
+
for (const type of Object.values(RECEIVER_INTEGRATION_TYPES)) expect(out.source).toMatch(new RegExp(`: ${type}\\[\\] = `));
|
|
295
|
+
// Fields the common four integrations gained, and structured values, keep their types.
|
|
296
|
+
expect(out.source).toContain('message_text: "{{ .CommonLabels.alertname }}"');
|
|
297
|
+
expect(out.source).toContain('threading: { enabled: true, thread_by_date: "daily" }');
|
|
298
|
+
expect(out.source).toContain("chat_id: -1001234567890");
|
|
299
|
+
expect(out.source).toContain('description: { template: \'{{ template "jira.default.description" . }}\', enable_update: false }');
|
|
300
|
+
expect(out.source).toContain('http_headers: {');
|
|
301
|
+
});
|
|
302
|
+
|
|
273
303
|
for (const file of UPSTREAM) {
|
|
274
304
|
test(`upstream sample: ${file}`, async () => {
|
|
275
305
|
const yaml = read("upstream", file);
|
|
276
|
-
|
|
277
|
-
const out = await expectRoundTrip(yaml,
|
|
278
|
-
expect(out.
|
|
306
|
+
const expected = UPSTREAM_EXPECT[file] ?? {};
|
|
307
|
+
const out = await expectRoundTrip(yaml, expected.prom001 ? { lintRules: ["PROM001"] } : {});
|
|
308
|
+
expect(out.lint.output.match(/PROM001/g) ?? []).toHaveLength(expected.prom001 ?? 0);
|
|
309
|
+
expect(out.warnings).toHaveLength(expected.warnings ?? 0);
|
|
310
|
+
for (const w of out.warnings) expect(w).toMatch(/match(_re)? are written as/);
|
|
311
|
+
// Every integration and global field is typed: nothing is carried as untyped data.
|
|
312
|
+
expect(out.source).not.toContain("Untyped");
|
|
279
313
|
if (file === "alertmanager-simple.yml") {
|
|
280
314
|
expect(out.source).toContain('service_key: "<team-X-key>"');
|
|
281
315
|
expect(out.source).toContain("const tracing: AlertmanagerTracingConfig = {");
|
|
282
|
-
expect(out.lint.output.match(/PROM001/g)).toHaveLength(4);
|
|
283
316
|
}
|
|
284
317
|
if (file === "alertmanager-route-labels.yml") {
|
|
285
318
|
expect(out.source).toContain('reason: "database {{ .GroupLabels.database }}"');
|
|
286
319
|
}
|
|
320
|
+
if (file === "alertmanager-sns-topic-arn.yml") {
|
|
321
|
+
expect(out.source).toContain("const snsApiNotificationsSns: SNSConfig[] = [");
|
|
322
|
+
}
|
|
287
323
|
});
|
|
288
324
|
}
|
|
289
325
|
});
|
|
@@ -300,6 +336,7 @@ describe("upstream tools accept the re-emitted files", () => {
|
|
|
300
336
|
const amFixtures = async (): Promise<BuiltFile[]> => [
|
|
301
337
|
...(await exampleOutputs()).filter((o) => o.name.endsWith("alertmanager.yml")),
|
|
302
338
|
handWritten("alertmanager-full.yml"),
|
|
339
|
+
handWritten("alertmanager-integrations.yml"),
|
|
303
340
|
...UPSTREAM.filter((f) => f.startsWith("alertmanager-")).map(upstream),
|
|
304
341
|
];
|
|
305
342
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# Deprecated spellings (match, match_re, source_match, the top-level
|
|
2
|
-
# mute_time_intervals), integrations
|
|
3
|
-
# msteams)
|
|
4
|
-
#
|
|
2
|
+
# mute_time_intervals), integrations beside the common four (opsgenie,
|
|
3
|
+
# msteams) and their global fields, *_file credentials, Go templates, a YAML
|
|
4
|
+
# anchor with a merge key, and one literal credential.
|
|
5
5
|
global:
|
|
6
6
|
resolve_timeout: 5m
|
|
7
7
|
smtp_smarthost: smtp.example.com:587
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
# Every receiver integration Alertmanager v0.34.1 defines, each with the
|
|
2
|
+
# fields its struct lists beyond the destination, every credential read from
|
|
3
|
+
# a *_file, and every global field that has a default for one of them.
|
|
4
|
+
global:
|
|
5
|
+
resolve_timeout: 5m
|
|
6
|
+
http_config:
|
|
7
|
+
follow_redirects: true
|
|
8
|
+
enable_http2: false
|
|
9
|
+
http_headers:
|
|
10
|
+
X-Tenant:
|
|
11
|
+
values: [platform]
|
|
12
|
+
X-Api-Key:
|
|
13
|
+
files: [/etc/alertmanager/secrets/api-key-header]
|
|
14
|
+
jira_api_url: https://jira.example.com/rest/api/2/
|
|
15
|
+
smtp_from: alertmanager@example.com
|
|
16
|
+
smtp_smarthost: smtp.example.com:465
|
|
17
|
+
smtp_auth_username: alertmanager
|
|
18
|
+
smtp_auth_password_file: /etc/alertmanager/secrets/smtp-password
|
|
19
|
+
smtp_require_tls: false
|
|
20
|
+
smtp_force_implicit_tls: true
|
|
21
|
+
smtp_tls_config:
|
|
22
|
+
ca_file: /etc/alertmanager/tls/ca.pem
|
|
23
|
+
min_version: TLS12
|
|
24
|
+
slack_app_token_file: /etc/alertmanager/secrets/slack-app-token
|
|
25
|
+
slack_app_url: https://slack.com/api/chat.postMessage
|
|
26
|
+
opsgenie_api_url: https://api.eu.opsgenie.com/
|
|
27
|
+
opsgenie_api_key_file: /etc/alertmanager/secrets/opsgenie-key
|
|
28
|
+
wechat_api_url: https://qyapi.weixin.qq.com/cgi-bin/
|
|
29
|
+
wechat_api_secret_file: /etc/alertmanager/secrets/wechat-secret
|
|
30
|
+
wechat_api_corp_id: corp-1
|
|
31
|
+
victorops_api_url: https://alert.victorops.com/integrations/generic/20131114/alert/
|
|
32
|
+
victorops_api_key_file: /etc/alertmanager/secrets/victorops-key
|
|
33
|
+
telegram_api_url: https://api.telegram.org
|
|
34
|
+
telegram_bot_token_file: /etc/alertmanager/secrets/telegram-token
|
|
35
|
+
webex_api_url: https://webexapis.com/v1/messages
|
|
36
|
+
rocketchat_api_url: https://open.rocket.chat
|
|
37
|
+
rocketchat_token_file: /etc/alertmanager/secrets/rocketchat-token
|
|
38
|
+
rocketchat_token_id_file: /etc/alertmanager/secrets/rocketchat-token-id
|
|
39
|
+
mattermost_webhook_url_file: /etc/alertmanager/secrets/mattermost-url
|
|
40
|
+
route:
|
|
41
|
+
receiver: chat
|
|
42
|
+
group_by: [alertname]
|
|
43
|
+
routes:
|
|
44
|
+
- matchers: ['team="ops"']
|
|
45
|
+
receiver: ops
|
|
46
|
+
- matchers: ['team="tickets"']
|
|
47
|
+
receiver: tickets
|
|
48
|
+
- matchers: ['team="mobile"']
|
|
49
|
+
receiver: mobile
|
|
50
|
+
receivers:
|
|
51
|
+
- name: chat
|
|
52
|
+
slack_configs:
|
|
53
|
+
- channel: '#alerts'
|
|
54
|
+
message_text: '{{ .CommonLabels.alertname }}'
|
|
55
|
+
callback_id: alerts
|
|
56
|
+
image_url: https://example.com/graph.png
|
|
57
|
+
thumb_url: https://example.com/thumb.png
|
|
58
|
+
update_message: true
|
|
59
|
+
timeout: 10s
|
|
60
|
+
fields:
|
|
61
|
+
- title: Severity
|
|
62
|
+
value: '{{ .CommonLabels.severity }}'
|
|
63
|
+
short: true
|
|
64
|
+
actions:
|
|
65
|
+
- type: button
|
|
66
|
+
text: Runbook
|
|
67
|
+
url: '{{ .CommonAnnotations.runbook_url }}'
|
|
68
|
+
style: primary
|
|
69
|
+
confirm:
|
|
70
|
+
text: Open the runbook?
|
|
71
|
+
ok_text: 'Yes'
|
|
72
|
+
dismiss_text: 'No'
|
|
73
|
+
discord_configs:
|
|
74
|
+
- webhook_url_file: /etc/alertmanager/secrets/discord-url
|
|
75
|
+
title: '{{ .CommonLabels.alertname }}'
|
|
76
|
+
message: '{{ .CommonAnnotations.summary }}'
|
|
77
|
+
content: '<@&123456>'
|
|
78
|
+
username: alertmanager
|
|
79
|
+
avatar_url: https://example.com/avatar.png
|
|
80
|
+
msteams_configs:
|
|
81
|
+
- webhook_url_file: /etc/alertmanager/secrets/teams-url
|
|
82
|
+
title: '{{ .CommonLabels.alertname }}'
|
|
83
|
+
summary: '{{ .CommonAnnotations.summary }}'
|
|
84
|
+
text: '{{ .CommonAnnotations.description }}'
|
|
85
|
+
msteamsv2_configs:
|
|
86
|
+
- webhook_url_file: /etc/alertmanager/secrets/teams-workflow-url
|
|
87
|
+
title: '{{ .CommonLabels.alertname }}'
|
|
88
|
+
text: '{{ .CommonAnnotations.description }}'
|
|
89
|
+
mattermost_configs:
|
|
90
|
+
- channel: alerts
|
|
91
|
+
username: alertmanager
|
|
92
|
+
text: '{{ .CommonAnnotations.summary }}'
|
|
93
|
+
icon_emoji: ':rotating_light:'
|
|
94
|
+
attachments:
|
|
95
|
+
- title: '{{ .CommonLabels.alertname }}'
|
|
96
|
+
color: danger
|
|
97
|
+
fields:
|
|
98
|
+
- title: Severity
|
|
99
|
+
value: '{{ .CommonLabels.severity }}'
|
|
100
|
+
short: true
|
|
101
|
+
props:
|
|
102
|
+
card: '{{ .CommonAnnotations.description }}'
|
|
103
|
+
priority:
|
|
104
|
+
priority: urgent
|
|
105
|
+
requested_ack: true
|
|
106
|
+
webex_configs:
|
|
107
|
+
- room_id: room-1
|
|
108
|
+
message: '{{ .CommonAnnotations.summary }}'
|
|
109
|
+
http_config:
|
|
110
|
+
authorization:
|
|
111
|
+
credentials_file: /etc/alertmanager/secrets/webex-token
|
|
112
|
+
rocketchat_configs:
|
|
113
|
+
- channel: '#alerts'
|
|
114
|
+
title: '{{ .CommonLabels.alertname }}'
|
|
115
|
+
emoji: ':fire:'
|
|
116
|
+
short_fields: true
|
|
117
|
+
fields:
|
|
118
|
+
- title: Severity
|
|
119
|
+
value: '{{ .CommonLabels.severity }}'
|
|
120
|
+
short: true
|
|
121
|
+
actions:
|
|
122
|
+
- type: button
|
|
123
|
+
text: Silence
|
|
124
|
+
url: https://alertmanager.example.com/#/silences/new
|
|
125
|
+
telegram_configs:
|
|
126
|
+
- chat_id: -1001234567890
|
|
127
|
+
message_thread_id: 7
|
|
128
|
+
parse_mode: HTML
|
|
129
|
+
disable_notifications: false
|
|
130
|
+
message: '{{ .CommonAnnotations.summary }}'
|
|
131
|
+
- name: ops
|
|
132
|
+
email_configs:
|
|
133
|
+
- to: ops@example.com
|
|
134
|
+
force_implicit_tls: true
|
|
135
|
+
threading:
|
|
136
|
+
enabled: true
|
|
137
|
+
thread_by_date: daily
|
|
138
|
+
opsgenie_configs:
|
|
139
|
+
- message: '{{ .CommonLabels.alertname }}'
|
|
140
|
+
priority: P2
|
|
141
|
+
tags: 'team,ops'
|
|
142
|
+
details:
|
|
143
|
+
cluster: '{{ .CommonLabels.cluster }}'
|
|
144
|
+
responders:
|
|
145
|
+
- name: ops
|
|
146
|
+
type: team
|
|
147
|
+
update_alerts: true
|
|
148
|
+
victorops_configs:
|
|
149
|
+
- routing_key: ops
|
|
150
|
+
message_type: CRITICAL
|
|
151
|
+
entity_display_name: '{{ .CommonLabels.alertname }}'
|
|
152
|
+
custom_fields:
|
|
153
|
+
cluster: '{{ .CommonLabels.cluster }}'
|
|
154
|
+
pagerduty_configs:
|
|
155
|
+
- routing_key_file: /etc/alertmanager/secrets/pagerduty-key
|
|
156
|
+
timeout: 20s
|
|
157
|
+
details:
|
|
158
|
+
firing: '{{ .Alerts.Firing | len }}'
|
|
159
|
+
incidentio_configs:
|
|
160
|
+
- url: https://api.incident.io/v2/alert_events/http/01ABC
|
|
161
|
+
alert_source_token_file: /etc/alertmanager/secrets/incidentio-token
|
|
162
|
+
max_alerts: 20
|
|
163
|
+
timeout: 15s
|
|
164
|
+
webhook_configs:
|
|
165
|
+
- url_file: /etc/alertmanager/secrets/webhook-url
|
|
166
|
+
timeout: 5s
|
|
167
|
+
http_config:
|
|
168
|
+
oauth2:
|
|
169
|
+
client_id: alertmanager
|
|
170
|
+
client_secret_file: /etc/alertmanager/secrets/oauth-secret
|
|
171
|
+
token_url: https://auth.example.com/token
|
|
172
|
+
scopes: [alerts]
|
|
173
|
+
- name: tickets
|
|
174
|
+
jira_configs:
|
|
175
|
+
- project: OPS
|
|
176
|
+
issue_type: Bug
|
|
177
|
+
summary: '{{ template "jira.default.summary" . }}'
|
|
178
|
+
description:
|
|
179
|
+
template: '{{ template "jira.default.description" . }}'
|
|
180
|
+
enable_update: false
|
|
181
|
+
labels: [alertmanager]
|
|
182
|
+
priority: High
|
|
183
|
+
resolve_transition: Done
|
|
184
|
+
reopen_transition: Reopen
|
|
185
|
+
wont_fix_resolution: "Won't Fix"
|
|
186
|
+
reopen_duration: 1h
|
|
187
|
+
fields:
|
|
188
|
+
customfield_10001: ops
|
|
189
|
+
http_config:
|
|
190
|
+
basic_auth:
|
|
191
|
+
username: alertmanager
|
|
192
|
+
password_file: /etc/alertmanager/secrets/jira-password
|
|
193
|
+
sns_configs:
|
|
194
|
+
- topic_arn: arn:aws:sns:us-east-1:123456789012:alerts
|
|
195
|
+
subject: '{{ .CommonLabels.alertname }}'
|
|
196
|
+
sigv4:
|
|
197
|
+
region: us-east-1
|
|
198
|
+
role_arn: arn:aws:iam::123456789012:role/alertmanager
|
|
199
|
+
attributes:
|
|
200
|
+
team: ops
|
|
201
|
+
- name: mobile
|
|
202
|
+
pushover_configs:
|
|
203
|
+
- user_key_file: /etc/alertmanager/secrets/pushover-user
|
|
204
|
+
token_file: /etc/alertmanager/secrets/pushover-token
|
|
205
|
+
priority: '{{ if eq .Status "firing" }}2{{ else }}0{{ end }}'
|
|
206
|
+
retry: 1m
|
|
207
|
+
expire: 1h
|
|
208
|
+
ttl: 24h
|
|
209
|
+
sound: siren
|
|
210
|
+
html: true
|
|
211
|
+
wechat_configs:
|
|
212
|
+
- to_user: '@all'
|
|
213
|
+
agent_id: '1000002'
|
|
214
|
+
message_type: markdown
|
|
215
|
+
message: '{{ .CommonAnnotations.summary }}'
|
|
@@ -111,4 +111,12 @@ export const UPSTREAM = [
|
|
|
111
111
|
"prometheus-alerting-templates.yml",
|
|
112
112
|
"alertmanager-simple.yml",
|
|
113
113
|
"alertmanager-route-labels.yml",
|
|
114
|
+
"alertmanager-conf-good.yml",
|
|
115
|
+
"alertmanager-mattermost-default-webhook-url-file.yml",
|
|
116
|
+
"alertmanager-opsgenie-default-apikey-file.yml",
|
|
117
|
+
"alertmanager-rocketchat-default-token-file.yml",
|
|
118
|
+
"alertmanager-sns-topic-arn.yml",
|
|
119
|
+
"alertmanager-telegram-default-bot-token-file.yml",
|
|
120
|
+
"alertmanager-victorops-default-apikey-file.yml",
|
|
121
|
+
"alertmanager-wechat-default-api-secret-file.yml",
|
|
114
122
|
];
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
# Vendored from github.com/prometheus/alertmanager v0.34.1, config/testdata/conf.good.yml
|
|
2
|
+
# (https://github.com/prometheus/alertmanager/blob/v0.34.1/config/testdata/conf.good.yml). Apache-2.0.
|
|
3
|
+
# Unchanged below this header.
|
|
4
|
+
global:
|
|
5
|
+
# The smarthost and SMTP sender used for mail notifications.
|
|
6
|
+
smtp_smarthost: 'localhost:25'
|
|
7
|
+
smtp_from: 'alertmanager@example.org'
|
|
8
|
+
smtp_auth_username: 'alertmanager'
|
|
9
|
+
smtp_auth_password: "multiline\nmysecret"
|
|
10
|
+
smtp_hello: "host.example.org"
|
|
11
|
+
slack_api_url: "http://mysecret.example.com/"
|
|
12
|
+
http_config:
|
|
13
|
+
proxy_url: 'http://127.0.0.1:1025'
|
|
14
|
+
# The directory from which notification templates are read.
|
|
15
|
+
templates:
|
|
16
|
+
- '/etc/alertmanager/template/*.tmpl'
|
|
17
|
+
# The root route on which each incoming alert enters.
|
|
18
|
+
route:
|
|
19
|
+
# The labels by which incoming alerts are grouped together. For example,
|
|
20
|
+
# multiple alerts coming in for cluster=A and alertname=LatencyHigh would
|
|
21
|
+
# be batched into a single group.
|
|
22
|
+
group_by: ['alertname', 'cluster', 'service']
|
|
23
|
+
# When a new group of alerts is created by an incoming alert, wait at
|
|
24
|
+
# least 'group_wait' to send the initial notification.
|
|
25
|
+
# This way ensures that you get multiple alerts for the same group that start
|
|
26
|
+
# firing shortly after another are batched together on the first
|
|
27
|
+
# notification.
|
|
28
|
+
group_wait: 30s
|
|
29
|
+
# When the first notification was sent, wait 'group_interval' to send a batch
|
|
30
|
+
# of new alerts that started firing for that group.
|
|
31
|
+
group_interval: 5m
|
|
32
|
+
# If an alert has successfully been sent, wait 'repeat_interval' to
|
|
33
|
+
# resend them.
|
|
34
|
+
repeat_interval: 3h
|
|
35
|
+
# A default receiver
|
|
36
|
+
receiver: team-X-mails
|
|
37
|
+
# All the above attributes are inherited by all child routes and can
|
|
38
|
+
# overwritten on each.
|
|
39
|
+
|
|
40
|
+
# The child route trees.
|
|
41
|
+
routes:
|
|
42
|
+
# This routes performs a regular expression match on alert labels to
|
|
43
|
+
# catch alerts that are related to a list of services.
|
|
44
|
+
- match_re:
|
|
45
|
+
service: ^(foo1|foo2|baz)$
|
|
46
|
+
receiver: team-X-mails
|
|
47
|
+
# The service has a sub-route for critical alerts, any alerts
|
|
48
|
+
# that do not match, i.e. severity != critical, fall-back to the
|
|
49
|
+
# parent node and are sent to 'team-X-mails'
|
|
50
|
+
routes:
|
|
51
|
+
- match:
|
|
52
|
+
severity: critical
|
|
53
|
+
receiver: team-X-pager
|
|
54
|
+
- match:
|
|
55
|
+
service: files
|
|
56
|
+
receiver: team-Y-mails
|
|
57
|
+
routes:
|
|
58
|
+
- match:
|
|
59
|
+
severity: critical
|
|
60
|
+
receiver: team-Y-pager
|
|
61
|
+
# This route handles all alerts coming from a database service. If there's
|
|
62
|
+
# no team to handle it, it defaults to the DB team.
|
|
63
|
+
- match:
|
|
64
|
+
service: database
|
|
65
|
+
receiver: team-DB-pager
|
|
66
|
+
# Also group alerts by affected database.
|
|
67
|
+
group_by: [alertname, cluster, database]
|
|
68
|
+
routes:
|
|
69
|
+
- match:
|
|
70
|
+
owner2: team-X
|
|
71
|
+
receiver: team-X-pager
|
|
72
|
+
continue: true
|
|
73
|
+
- match:
|
|
74
|
+
owner: team-Y
|
|
75
|
+
receiver: team-Y-pager
|
|
76
|
+
# continue: true
|
|
77
|
+
# Inhibition rules allow to mute a set of alerts given that another alert is
|
|
78
|
+
# firing.
|
|
79
|
+
# We use this to mute any warning-level notifications if the same alert is
|
|
80
|
+
# already critical.
|
|
81
|
+
inhibit_rules:
|
|
82
|
+
- source_match:
|
|
83
|
+
severity: 'critical'
|
|
84
|
+
target_match:
|
|
85
|
+
severity: 'warning'
|
|
86
|
+
# Apply inhibition if the alertname is the same.
|
|
87
|
+
equal: ['alertname', 'cluster', 'service']
|
|
88
|
+
receivers:
|
|
89
|
+
- name: 'team-X-mails'
|
|
90
|
+
email_configs:
|
|
91
|
+
- to: 'team-X+alerts@example.org'
|
|
92
|
+
- name: 'team-X-pager'
|
|
93
|
+
email_configs:
|
|
94
|
+
- to: 'team-X+alerts-critical@example.org'
|
|
95
|
+
pagerduty_configs:
|
|
96
|
+
- routing_key: "mysecret"
|
|
97
|
+
- name: 'team-Y-mails'
|
|
98
|
+
email_configs:
|
|
99
|
+
- to: 'team-Y+alerts@example.org'
|
|
100
|
+
- name: 'team-Y-pager'
|
|
101
|
+
pagerduty_configs:
|
|
102
|
+
- routing_key: "mysecret"
|
|
103
|
+
- name: 'team-DB-pager'
|
|
104
|
+
pagerduty_configs:
|
|
105
|
+
- routing_key: "mysecret"
|
|
106
|
+
- name: victorOps-receiver
|
|
107
|
+
victorops_configs:
|
|
108
|
+
- api_key: mysecret
|
|
109
|
+
routing_key: Sample_route
|
|
110
|
+
- name: opsGenie-receiver
|
|
111
|
+
opsgenie_configs:
|
|
112
|
+
- api_key: mysecret
|
|
113
|
+
- name: pushover-receiver
|
|
114
|
+
pushover_configs:
|
|
115
|
+
- token: mysecret
|
|
116
|
+
user_key: key
|
|
117
|
+
- name: slack-receiver
|
|
118
|
+
slack_configs:
|
|
119
|
+
- channel: '#my-channel'
|
|
120
|
+
image_url: 'http://some.img.com/img.png'
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Vendored from github.com/prometheus/alertmanager v0.34.1, config/testdata/conf.mattermost-default-webhook-url-file.yml
|
|
2
|
+
# (https://github.com/prometheus/alertmanager/blob/v0.34.1/config/testdata/conf.mattermost-default-webhook-url-file.yml). Apache-2.0.
|
|
3
|
+
# Unchanged below this header.
|
|
4
|
+
global:
|
|
5
|
+
mattermost_webhook_url_file: /global_file
|
|
6
|
+
route:
|
|
7
|
+
receiver: team-X-mattermost
|
|
8
|
+
|
|
9
|
+
receivers:
|
|
10
|
+
- name: team-X-mattermost
|
|
11
|
+
mattermost_configs:
|
|
12
|
+
- channel: team-X
|
|
13
|
+
- name: team-Y-mattermost
|
|
14
|
+
mattermost_configs:
|
|
15
|
+
- channel: team-Y
|
|
16
|
+
webhook_url_file: /override_file
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# Vendored from github.com/prometheus/alertmanager v0.34.1, config/testdata/conf.opsgenie-default-apikey-file.yml
|
|
2
|
+
# (https://github.com/prometheus/alertmanager/blob/v0.34.1/config/testdata/conf.opsgenie-default-apikey-file.yml). Apache-2.0.
|
|
3
|
+
# Unchanged below this header.
|
|
4
|
+
global:
|
|
5
|
+
opsgenie_api_key_file: '/global_file'
|
|
6
|
+
route:
|
|
7
|
+
group_by: ['alertname', 'cluster', 'service']
|
|
8
|
+
group_wait: 30s
|
|
9
|
+
group_interval: 5m
|
|
10
|
+
repeat_interval: 3h
|
|
11
|
+
receiver: escalation-Y-opsgenie
|
|
12
|
+
routes:
|
|
13
|
+
- match:
|
|
14
|
+
service: foo
|
|
15
|
+
receiver: team-X-opsgenie
|
|
16
|
+
receivers:
|
|
17
|
+
- name: 'team-X-opsgenie'
|
|
18
|
+
opsgenie_configs:
|
|
19
|
+
- responders:
|
|
20
|
+
- name: 'team-X'
|
|
21
|
+
type: 'team'
|
|
22
|
+
- name: 'escalation-Y-opsgenie'
|
|
23
|
+
opsgenie_configs:
|
|
24
|
+
- responders:
|
|
25
|
+
- name: 'escalation-Y'
|
|
26
|
+
type: 'escalation'
|
|
27
|
+
api_key_file: /override_file
|