@intentius/chant-lexicon-prometheus 0.124.31 → 0.124.32
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/alertmanager.js +61 -0
- package/dist/api/alertmanager.js +98 -0
- package/dist/api/apply.js +306 -0
- package/dist/api/bind.js +66 -0
- package/dist/api/client.js +110 -0
- package/dist/api/evaluated.js +86 -0
- package/dist/api/fake-servers.js +165 -0
- package/dist/api/index.js +17 -0
- package/dist/api/ruler.js +122 -0
- package/dist/build.js +240 -0
- package/dist/catalog.js +66 -0
- package/dist/codegen/docs-cli.js +3 -0
- package/dist/codegen/docs.js +105 -0
- package/dist/codegen/generate-cli.js +7 -0
- package/dist/codegen/generate.js +42 -0
- package/dist/codegen/package.js +28 -0
- package/dist/collector-metrics.js +225 -0
- package/dist/composites/alert-routing.js +210 -0
- package/dist/composites/catalog.js +344 -0
- package/dist/composites/genai.js +454 -0
- package/dist/composites/index.js +8 -0
- package/dist/composites/red-alerts.js +135 -0
- package/dist/composites/slo.js +353 -0
- package/dist/composites/watchdog.js +110 -0
- package/dist/config-model.js +52 -0
- package/dist/config.js +225 -0
- package/dist/describe-resources.js +299 -0
- package/dist/detect.js +12 -0
- package/dist/duration.js +54 -0
- package/dist/export-resources.js +163 -0
- package/dist/import/alertmanager-live.js +309 -0
- package/dist/import/embedded.js +100 -0
- package/dist/import/generator.js +683 -0
- package/dist/import/parser.js +428 -0
- package/dist/import/slo.js +207 -0
- package/dist/index.js +33 -0
- package/dist/init-templates.js +119 -0
- package/dist/integrations.js +33 -0
- package/dist/integrity.json +2 -2
- package/dist/lint/audit-catalog.js +60 -0
- package/dist/lint/audit-lineage.js +42 -0
- package/dist/lint/post-synth/index.js +68 -0
- package/dist/lint/post-synth/prom-helpers.js +157 -0
- package/dist/lint/post-synth/prom101.js +13 -0
- package/dist/lint/post-synth/prom102.js +13 -0
- package/dist/lint/post-synth/prom103.js +13 -0
- package/dist/lint/post-synth/prom104.js +13 -0
- package/dist/lint/post-synth/prom105.js +13 -0
- package/dist/lint/post-synth/prom106.js +13 -0
- package/dist/lint/post-synth/prom107.js +13 -0
- package/dist/lint/post-synth/prom201.js +13 -0
- package/dist/lint/post-synth/prom202.js +13 -0
- package/dist/lint/post-synth/prom203.js +13 -0
- package/dist/lint/post-synth/prom204.js +13 -0
- package/dist/lint/post-synth/prom205.js +13 -0
- package/dist/lint/post-synth/prom206.js +13 -0
- package/dist/lint/post-synth/prom207.js +13 -0
- package/dist/lint/post-synth/prom208.js +13 -0
- package/dist/lint/post-synth/prom209.js +13 -0
- package/dist/lint/post-synth/prom210.js +13 -0
- package/dist/lint/post-synth/prom211.js +17 -0
- package/dist/lint/post-synth/prom212.js +14 -0
- package/dist/lint/post-synth/prom213.js +16 -0
- package/dist/lint/post-synth/prom214.js +14 -0
- package/dist/lint/post-synth/prom215.js +14 -0
- package/dist/lint/post-synth/prom216.js +15 -0
- package/dist/lint/post-synth/prom217.js +14 -0
- package/dist/lint/post-synth/prom218.js +15 -0
- package/dist/lint/post-synth/prom219.js +14 -0
- package/dist/lint/post-synth/prom220.js +14 -0
- package/dist/lint/post-synth/prom221.js +15 -0
- package/dist/lint/post-synth/prom222.js +14 -0
- package/dist/lint/post-synth/prom223.js +15 -0
- package/dist/lint/post-synth/prom224.js +14 -0
- package/dist/lint/post-synth/prom225.js +15 -0
- package/dist/lint/post-synth/prom301.js +19 -0
- package/dist/lint/rules/index.js +8 -0
- package/dist/lint/rules/literal-credential.js +127 -0
- package/dist/lint/rules/prom-ast.js +65 -0
- package/dist/lint/rules/promql-literal.js +65 -0
- package/dist/lint/rules/slo-literal.js +79 -0
- package/dist/lsp/completions.js +11 -0
- package/dist/lsp/hover.js +21 -0
- package/dist/manifest.json +1 -1
- package/dist/matchers.js +131 -0
- package/dist/model.js +108 -0
- package/dist/op/activities/index.js +20 -0
- package/dist/op/activities/promtool.js +87 -0
- package/dist/op/activities/rule-audit.js +128 -0
- package/dist/op/activities/rules-loaded.js +73 -0
- package/dist/op/activities/silence.js +123 -0
- package/dist/op/activity-contracts.js +51 -0
- package/dist/op/audit-op.js +28 -0
- package/dist/op/builders.js +57 -0
- package/dist/op/slo-rule-tests.js +148 -0
- package/dist/package-cli.js +14 -0
- package/dist/pin.js +13 -0
- package/dist/plugin.js +143 -0
- package/dist/prometheus-config.js +38 -0
- package/dist/promql-analysis.js +343 -0
- package/dist/promql.js +44 -0
- package/dist/rule-eval.js +466 -0
- package/dist/rules.js +97 -0
- package/dist/serializer.js +59 -0
- package/dist/skill-defs.js +41 -0
- package/dist/tools.js +85 -0
- package/dist/validate-cli.js +7 -0
- package/dist/validate-config.js +556 -0
- package/dist/validate-integrations.js +388 -0
- package/dist/validate-relabel.js +52 -0
- package/dist/validate-security.js +135 -0
- package/dist/validate.js +69 -0
- package/package.json +10 -10
- package/src/codegen/package.ts +2 -2
|
@@ -0,0 +1,428 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rule file, `prometheus.yml` or `alertmanager.yml` -> `TemplateIR`, for `chant import`.
|
|
3
|
+
*
|
|
4
|
+
* Either file is one document whose parts name each other (a route names a
|
|
5
|
+
* receiver and time intervals), so the IR carries the whole file as one
|
|
6
|
+
* resource and the generator lays out the modules itself, turning those
|
|
7
|
+
* names into imports of declared entities. Core splits an IR with more than
|
|
8
|
+
* three resources into one generate() call per category and keeps only the
|
|
9
|
+
* first file of each, which would lose the imports between a route and the
|
|
10
|
+
* receivers it names.
|
|
11
|
+
*
|
|
12
|
+
* Values are carried as written: `*_file` credential paths, Go templates in
|
|
13
|
+
* annotations and notifier fields, and literal credentials, which PROM001
|
|
14
|
+
* then reports in the generated source. What the lexicon has no place for is
|
|
15
|
+
* named in `warnings`. Two deprecated Alertmanager spellings are rewritten to
|
|
16
|
+
* the ones Alertmanager treats the same way: `match` / `match_re` (and the
|
|
17
|
+
* inhibit rules' `source_match*` / `target_match*`) become matcher strings,
|
|
18
|
+
* and the top-level `mute_time_intervals` list joins `time_intervals`.
|
|
19
|
+
*/
|
|
20
|
+
import * as jsYaml from "js-yaml";
|
|
21
|
+
import { ALERTMANAGER_GLOBAL_FIELDS, RECEIVER_INTEGRATIONS, looksLikeAlertmanagerConfig, looksLikePrometheusConfig, looksLikeRuleFile, PROMETHEUS_CONFIG_SECTIONS, } from "../model.js";
|
|
22
|
+
import { TYPED_SD_KINDS } from "../model.js";
|
|
23
|
+
import { PROMETHEUS_PIN } from "../pin.js";
|
|
24
|
+
/** The IR resource type for a whole rule file. */
|
|
25
|
+
export const RULE_FILE_RESOURCE_TYPE = "Prometheus::RuleFile";
|
|
26
|
+
/** The IR resource type for a whole `alertmanager.yml`. */
|
|
27
|
+
export const ALERTMANAGER_RESOURCE_TYPE = "Prometheus::Alertmanager::Config";
|
|
28
|
+
/** The IR resource type for a whole `prometheus.yml`. */
|
|
29
|
+
export const PROMETHEUS_RESOURCE_TYPE = "Prometheus::Config";
|
|
30
|
+
// YAML 1.2 core types plus `<<` merge keys, which Alertmanager configs use to
|
|
31
|
+
// share notifier settings. The default schema would also turn an unquoted
|
|
32
|
+
// date into a Date, which neither Go loader does.
|
|
33
|
+
// js-yaml exports its built-in types at runtime; @types/js-yaml does not declare them.
|
|
34
|
+
const MERGE_TYPE = jsYaml.types.merge;
|
|
35
|
+
const YAML_SCHEMA = jsYaml.CORE_SCHEMA.extend({ implicit: [MERGE_TYPE] });
|
|
36
|
+
function isPlainObject(v) {
|
|
37
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
38
|
+
}
|
|
39
|
+
/** A label set with every value a string, as Prometheus and Alertmanager read one. */
|
|
40
|
+
function labelSet(v, at, warnings) {
|
|
41
|
+
if (v === null || v === undefined)
|
|
42
|
+
return undefined;
|
|
43
|
+
if (!isPlainObject(v)) {
|
|
44
|
+
warnings.push(`${at} is not a mapping; it is not carried`);
|
|
45
|
+
return undefined;
|
|
46
|
+
}
|
|
47
|
+
const out = {};
|
|
48
|
+
for (const [k, x] of Object.entries(v))
|
|
49
|
+
out[k] = x === null ? "" : String(x);
|
|
50
|
+
return out;
|
|
51
|
+
}
|
|
52
|
+
function stringList(v, at, warnings) {
|
|
53
|
+
if (v === null || v === undefined)
|
|
54
|
+
return undefined;
|
|
55
|
+
if (!Array.isArray(v)) {
|
|
56
|
+
warnings.push(`${at} is not a list; it is not carried`);
|
|
57
|
+
return undefined;
|
|
58
|
+
}
|
|
59
|
+
return v.map((x) => String(x));
|
|
60
|
+
}
|
|
61
|
+
// ── rule files ──────────────────────────────────────────────────────
|
|
62
|
+
const GROUP_KEYS = ["name", "interval", "query_offset", "limit", "labels", "rules"];
|
|
63
|
+
const RULE_KEYS = ["record", "alert", "expr", "for", "keep_firing_for", "labels", "annotations"];
|
|
64
|
+
function parseRule(raw, at, warnings) {
|
|
65
|
+
if (!isPlainObject(raw)) {
|
|
66
|
+
warnings.push(`${at} is not a mapping; it is not carried`);
|
|
67
|
+
return undefined;
|
|
68
|
+
}
|
|
69
|
+
for (const k of Object.keys(raw)) {
|
|
70
|
+
if (!RULE_KEYS.includes(k))
|
|
71
|
+
warnings.push(`${at}.${k} is not a rule field; it is not carried`);
|
|
72
|
+
}
|
|
73
|
+
const out = {};
|
|
74
|
+
for (const k of ["record", "alert", "expr", "for", "keep_firing_for"]) {
|
|
75
|
+
const v = raw[k];
|
|
76
|
+
if (v !== undefined && v !== null)
|
|
77
|
+
out[k] = String(v);
|
|
78
|
+
}
|
|
79
|
+
const labels = labelSet(raw.labels, `${at}.labels`, warnings);
|
|
80
|
+
if (labels)
|
|
81
|
+
out.labels = labels;
|
|
82
|
+
const annotations = labelSet(raw.annotations, `${at}.annotations`, warnings);
|
|
83
|
+
if (annotations)
|
|
84
|
+
out.annotations = annotations;
|
|
85
|
+
return out;
|
|
86
|
+
}
|
|
87
|
+
function parseRuleFile(doc, warnings) {
|
|
88
|
+
for (const k of Object.keys(doc)) {
|
|
89
|
+
if (k !== "groups")
|
|
90
|
+
warnings.push(`top-level key "${k}" is not part of a rule file; it is not carried`);
|
|
91
|
+
}
|
|
92
|
+
const groups = [];
|
|
93
|
+
const list = Array.isArray(doc.groups) ? doc.groups : [];
|
|
94
|
+
list.forEach((raw, i) => {
|
|
95
|
+
const at = `groups[${i}]`;
|
|
96
|
+
if (!isPlainObject(raw)) {
|
|
97
|
+
warnings.push(`${at} is not a mapping; it is not carried`);
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
const name = String(raw.name ?? "");
|
|
101
|
+
const where = `group "${name}"`;
|
|
102
|
+
for (const k of Object.keys(raw)) {
|
|
103
|
+
if (!GROUP_KEYS.includes(k))
|
|
104
|
+
warnings.push(`${where}: "${k}" is not a rule group field; it is not carried`);
|
|
105
|
+
}
|
|
106
|
+
const group = { name };
|
|
107
|
+
if (raw.interval !== undefined && raw.interval !== null)
|
|
108
|
+
group.interval = String(raw.interval);
|
|
109
|
+
if (raw.query_offset !== undefined && raw.query_offset !== null)
|
|
110
|
+
group.query_offset = String(raw.query_offset);
|
|
111
|
+
if (raw.limit !== undefined && raw.limit !== null)
|
|
112
|
+
group.limit = Number(raw.limit);
|
|
113
|
+
const labels = labelSet(raw.labels, `${where} labels`, warnings);
|
|
114
|
+
if (labels)
|
|
115
|
+
group.labels = labels;
|
|
116
|
+
const rules = [];
|
|
117
|
+
(Array.isArray(raw.rules) ? raw.rules : []).forEach((r, j) => {
|
|
118
|
+
const rule = parseRule(r, `${where} rules[${j}]`, warnings);
|
|
119
|
+
if (rule)
|
|
120
|
+
rules.push(rule);
|
|
121
|
+
});
|
|
122
|
+
group.rules = rules;
|
|
123
|
+
groups.push(group);
|
|
124
|
+
});
|
|
125
|
+
return { groups };
|
|
126
|
+
}
|
|
127
|
+
// ── alertmanager.yml ────────────────────────────────────────────────
|
|
128
|
+
const AM_SECTIONS = ["global", "templates", "route", "inhibit_rules", "receivers", "time_intervals", "mute_time_intervals", "tracing"];
|
|
129
|
+
const ROUTE_KEYS = [
|
|
130
|
+
"receiver",
|
|
131
|
+
"group_by",
|
|
132
|
+
"continue",
|
|
133
|
+
"matchers",
|
|
134
|
+
"match",
|
|
135
|
+
"match_re",
|
|
136
|
+
"group_wait",
|
|
137
|
+
"group_interval",
|
|
138
|
+
"repeat_interval",
|
|
139
|
+
"mute_time_intervals",
|
|
140
|
+
"active_time_intervals",
|
|
141
|
+
"labels",
|
|
142
|
+
"routes",
|
|
143
|
+
];
|
|
144
|
+
/** A matcher string Alertmanager parses back to `name op value`. */
|
|
145
|
+
export function matcherString(name, op, value) {
|
|
146
|
+
const escaped = value.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, "\\n");
|
|
147
|
+
return `${name}${op}"${escaped}"`;
|
|
148
|
+
}
|
|
149
|
+
/** `match` and `match_re` maps as matcher strings, in the order written. */
|
|
150
|
+
function matchersFrom(match, matchRe, at, warnings) {
|
|
151
|
+
const out = [];
|
|
152
|
+
for (const [value, op, key] of [
|
|
153
|
+
[match, "=", "match"],
|
|
154
|
+
[matchRe, "=~", "match_re"],
|
|
155
|
+
]) {
|
|
156
|
+
if (value === undefined || value === null)
|
|
157
|
+
continue;
|
|
158
|
+
if (!isPlainObject(value)) {
|
|
159
|
+
warnings.push(`${at}.${key} is not a mapping; it is not carried`);
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
for (const [k, v] of Object.entries(value))
|
|
163
|
+
out.push(matcherString(k, op, String(v)));
|
|
164
|
+
}
|
|
165
|
+
return out;
|
|
166
|
+
}
|
|
167
|
+
function parseRoute(raw, at, warnings) {
|
|
168
|
+
if (!isPlainObject(raw)) {
|
|
169
|
+
warnings.push(`${at} is not a mapping; it is carried as an empty route`);
|
|
170
|
+
return {};
|
|
171
|
+
}
|
|
172
|
+
for (const k of Object.keys(raw)) {
|
|
173
|
+
if (!ROUTE_KEYS.includes(k))
|
|
174
|
+
warnings.push(`${at}.${k} is not a route field; it is not carried`);
|
|
175
|
+
}
|
|
176
|
+
const out = {};
|
|
177
|
+
if (raw.receiver !== undefined && raw.receiver !== null)
|
|
178
|
+
out.receiver = String(raw.receiver);
|
|
179
|
+
const groupBy = stringList(raw.group_by, `${at}.group_by`, warnings);
|
|
180
|
+
if (groupBy)
|
|
181
|
+
out.group_by = groupBy;
|
|
182
|
+
if (typeof raw.continue === "boolean")
|
|
183
|
+
out.continue = raw.continue;
|
|
184
|
+
const matchers = stringList(raw.matchers, `${at}.matchers`, warnings) ?? [];
|
|
185
|
+
const converted = matchersFrom(raw.match, raw.match_re, at, warnings);
|
|
186
|
+
if (converted.length > 0) {
|
|
187
|
+
warnings.push(`${at}: match/match_re are written as matchers (${converted.join(", ")}), which Alertmanager treats the same way`);
|
|
188
|
+
}
|
|
189
|
+
if (matchers.length + converted.length > 0 || raw.matchers !== undefined)
|
|
190
|
+
out.matchers = [...matchers, ...converted];
|
|
191
|
+
for (const k of ["group_wait", "group_interval", "repeat_interval"]) {
|
|
192
|
+
if (raw[k] !== undefined && raw[k] !== null)
|
|
193
|
+
out[k] = String(raw[k]);
|
|
194
|
+
}
|
|
195
|
+
const mute = stringList(raw.mute_time_intervals, `${at}.mute_time_intervals`, warnings);
|
|
196
|
+
if (mute)
|
|
197
|
+
out.mute_time_intervals = mute;
|
|
198
|
+
const active = stringList(raw.active_time_intervals, `${at}.active_time_intervals`, warnings);
|
|
199
|
+
if (active)
|
|
200
|
+
out.active_time_intervals = active;
|
|
201
|
+
const labels = labelSet(raw.labels, `${at}.labels`, warnings);
|
|
202
|
+
if (labels)
|
|
203
|
+
out.labels = labels;
|
|
204
|
+
if (Array.isArray(raw.routes))
|
|
205
|
+
out.routes = raw.routes.map((r, i) => parseRoute(r, `${at}.routes[${i}]`, warnings));
|
|
206
|
+
else if (raw.routes !== undefined && raw.routes !== null)
|
|
207
|
+
warnings.push(`${at}.routes is not a list; it is not carried`);
|
|
208
|
+
return out;
|
|
209
|
+
}
|
|
210
|
+
function parseInhibitRule(raw, at, warnings) {
|
|
211
|
+
if (!isPlainObject(raw)) {
|
|
212
|
+
warnings.push(`${at} is not a mapping; it is not carried`);
|
|
213
|
+
return undefined;
|
|
214
|
+
}
|
|
215
|
+
const known = ["name", "source_matchers", "source_match", "source_match_re", "target_matchers", "target_match", "target_match_re", "equal"];
|
|
216
|
+
for (const k of Object.keys(raw)) {
|
|
217
|
+
if (!known.includes(k))
|
|
218
|
+
warnings.push(`${at}.${k} is not an inhibit rule field; it is not carried`);
|
|
219
|
+
}
|
|
220
|
+
const out = {};
|
|
221
|
+
if (raw.name !== undefined && raw.name !== null)
|
|
222
|
+
out.name = String(raw.name);
|
|
223
|
+
for (const side of ["source", "target"]) {
|
|
224
|
+
const matchers = stringList(raw[`${side}_matchers`], `${at}.${side}_matchers`, warnings) ?? [];
|
|
225
|
+
const converted = matchersFrom(raw[`${side}_match`], raw[`${side}_match_re`], at, warnings);
|
|
226
|
+
if (converted.length > 0) {
|
|
227
|
+
warnings.push(`${at}: ${side}_match/${side}_match_re are written as ${side}_matchers (${converted.join(", ")}), which Alertmanager treats the same way`);
|
|
228
|
+
}
|
|
229
|
+
if (matchers.length + converted.length > 0 || raw[`${side}_matchers`] !== undefined) {
|
|
230
|
+
out[`${side}_matchers`] = [...matchers, ...converted];
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
const equal = stringList(raw.equal, `${at}.equal`, warnings);
|
|
234
|
+
if (equal)
|
|
235
|
+
out.equal = equal;
|
|
236
|
+
return out;
|
|
237
|
+
}
|
|
238
|
+
function parseTimeIntervals(raw, at, warnings) {
|
|
239
|
+
if (raw === undefined || raw === null)
|
|
240
|
+
return [];
|
|
241
|
+
if (!Array.isArray(raw)) {
|
|
242
|
+
warnings.push(`${at} is not a list; it is not carried`);
|
|
243
|
+
return [];
|
|
244
|
+
}
|
|
245
|
+
const out = [];
|
|
246
|
+
raw.forEach((t, i) => {
|
|
247
|
+
if (!isPlainObject(t) || typeof t.name !== "string") {
|
|
248
|
+
warnings.push(`${at}[${i}] has no name; it is not carried`);
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
out.push({ name: t.name, time_intervals: (Array.isArray(t.time_intervals) ? t.time_intervals : []) });
|
|
252
|
+
});
|
|
253
|
+
return out;
|
|
254
|
+
}
|
|
255
|
+
const AM_VERSION = PROMETHEUS_PIN.alertmanager.version;
|
|
256
|
+
const RECEIVER_KEYS = new Set(["name", "labels", ...RECEIVER_INTEGRATIONS]);
|
|
257
|
+
const GLOBAL_KEYS = new Set(ALERTMANAGER_GLOBAL_FIELDS);
|
|
258
|
+
function parseAlertmanager(doc, warnings) {
|
|
259
|
+
for (const k of Object.keys(doc)) {
|
|
260
|
+
if (!AM_SECTIONS.includes(k))
|
|
261
|
+
warnings.push(`top-level section "${k}" is not one chant declares; it is not carried`);
|
|
262
|
+
}
|
|
263
|
+
const config = {};
|
|
264
|
+
if (isPlainObject(doc.global)) {
|
|
265
|
+
config.global = doc.global;
|
|
266
|
+
const unknown = Object.keys(doc.global).filter((k) => !GLOBAL_KEYS.has(k));
|
|
267
|
+
if (unknown.length > 0) {
|
|
268
|
+
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`);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
else if (doc.global !== undefined && doc.global !== null)
|
|
272
|
+
warnings.push("global is not a mapping; it is not carried");
|
|
273
|
+
const templates = stringList(doc.templates, "templates", warnings);
|
|
274
|
+
if (templates)
|
|
275
|
+
config.templates = templates;
|
|
276
|
+
if (doc.route !== undefined && doc.route !== null)
|
|
277
|
+
config.route = parseRoute(doc.route, "route", warnings);
|
|
278
|
+
if (Array.isArray(doc.inhibit_rules)) {
|
|
279
|
+
config.inhibit_rules = doc.inhibit_rules
|
|
280
|
+
.map((r, i) => parseInhibitRule(r, `inhibit_rules[${i}]`, warnings))
|
|
281
|
+
.filter((r) => r !== undefined);
|
|
282
|
+
}
|
|
283
|
+
else if (doc.inhibit_rules !== undefined && doc.inhibit_rules !== null) {
|
|
284
|
+
warnings.push("inhibit_rules is not a list; it is not carried");
|
|
285
|
+
}
|
|
286
|
+
if (Array.isArray(doc.receivers)) {
|
|
287
|
+
const receivers = [];
|
|
288
|
+
doc.receivers.forEach((r, i) => {
|
|
289
|
+
if (!isPlainObject(r) || r.name === undefined || r.name === null) {
|
|
290
|
+
warnings.push(`receivers[${i}] has no name; it is not carried`);
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
const unknown = Object.keys(r).filter((k) => !RECEIVER_KEYS.has(k));
|
|
294
|
+
if (unknown.length > 0) {
|
|
295
|
+
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`);
|
|
296
|
+
}
|
|
297
|
+
receivers.push({ ...r, name: String(r.name) });
|
|
298
|
+
});
|
|
299
|
+
config.receivers = receivers;
|
|
300
|
+
}
|
|
301
|
+
else if (doc.receivers !== undefined && doc.receivers !== null) {
|
|
302
|
+
warnings.push("receivers is not a list; it is not carried");
|
|
303
|
+
}
|
|
304
|
+
const intervals = parseTimeIntervals(doc.time_intervals, "time_intervals", warnings);
|
|
305
|
+
const legacy = parseTimeIntervals(doc.mute_time_intervals, "mute_time_intervals", warnings);
|
|
306
|
+
if (legacy.length > 0) {
|
|
307
|
+
warnings.push(`the top-level mute_time_intervals (${legacy.map((t) => t.name).join(", ")}) are declared as TimeIntervals and written under time_intervals, which Alertmanager reads the same way`);
|
|
308
|
+
}
|
|
309
|
+
if (intervals.length + legacy.length > 0)
|
|
310
|
+
config.time_intervals = [...intervals, ...legacy];
|
|
311
|
+
if (isPlainObject(doc.tracing))
|
|
312
|
+
config.tracing = doc.tracing;
|
|
313
|
+
else if (doc.tracing !== undefined && doc.tracing !== null)
|
|
314
|
+
warnings.push("tracing is not a mapping; it is not carried");
|
|
315
|
+
return config;
|
|
316
|
+
}
|
|
317
|
+
// ── prometheus.yml ──────────────────────────────────────────────────
|
|
318
|
+
function parsePrometheusConfig(doc, warnings) {
|
|
319
|
+
const config = {};
|
|
320
|
+
for (const k of Object.keys(doc)) {
|
|
321
|
+
if (!PROMETHEUS_CONFIG_SECTIONS.includes(k))
|
|
322
|
+
warnings.push(`top-level section "${k}" is not one Prometheus ${PROM_VERSION} defines; it is not carried`);
|
|
323
|
+
}
|
|
324
|
+
const jobNames = new Set();
|
|
325
|
+
for (const key of PROMETHEUS_CONFIG_SECTIONS) {
|
|
326
|
+
const v = doc[key];
|
|
327
|
+
if (v === undefined || v === null)
|
|
328
|
+
continue;
|
|
329
|
+
if (key === "rule_files" || key === "scrape_config_files") {
|
|
330
|
+
const list = stringList(v, key, warnings);
|
|
331
|
+
if (list)
|
|
332
|
+
config[key] = list;
|
|
333
|
+
}
|
|
334
|
+
else if (key === "scrape_configs") {
|
|
335
|
+
if (!Array.isArray(v)) {
|
|
336
|
+
warnings.push("scrape_configs is not a list; it is not carried");
|
|
337
|
+
continue;
|
|
338
|
+
}
|
|
339
|
+
const jobs = [];
|
|
340
|
+
v.forEach((raw, i) => {
|
|
341
|
+
if (!isPlainObject(raw) || typeof raw.job_name !== "string") {
|
|
342
|
+
warnings.push(`scrape_configs[${i}] has no job_name; it is not carried`);
|
|
343
|
+
return;
|
|
344
|
+
}
|
|
345
|
+
if (jobNames.has(raw.job_name))
|
|
346
|
+
warnings.push(`scrape_configs: job_name "${raw.job_name}" is used more than once; Prometheus rejects that`);
|
|
347
|
+
jobNames.add(raw.job_name);
|
|
348
|
+
const sd = Object.keys(raw).filter((k) => k.endsWith("_sd_configs") && !TYPED_SD_KINDS.includes(k));
|
|
349
|
+
if (sd.length > 0)
|
|
350
|
+
warnings.push(`job "${raw.job_name}": ${sd.join(", ")} ${sd.length === 1 ? "is" : "are"} carried as data, untyped`);
|
|
351
|
+
jobs.push(raw);
|
|
352
|
+
});
|
|
353
|
+
config.scrape_configs = jobs;
|
|
354
|
+
}
|
|
355
|
+
else if (key === "remote_write" || key === "remote_read") {
|
|
356
|
+
if (Array.isArray(v))
|
|
357
|
+
config[key] = v;
|
|
358
|
+
else
|
|
359
|
+
warnings.push(`${key} is not a list; it is not carried`);
|
|
360
|
+
}
|
|
361
|
+
else if (isPlainObject(v)) {
|
|
362
|
+
config[key] = v;
|
|
363
|
+
}
|
|
364
|
+
else {
|
|
365
|
+
warnings.push(`${key} is not a mapping; it is not carried`);
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
return config;
|
|
369
|
+
}
|
|
370
|
+
const PROM_VERSION = PROMETHEUS_PIN.prometheus.version;
|
|
371
|
+
// ── entry points ────────────────────────────────────────────────────
|
|
372
|
+
/** Load YAML the way the parser does: YAML 1.2 core types plus `<<` merge keys. */
|
|
373
|
+
export function loadPrometheusYaml(content) {
|
|
374
|
+
return content.trim() === "" ? {} : jsYaml.load(content, { schema: YAML_SCHEMA });
|
|
375
|
+
}
|
|
376
|
+
/** A rule file already loaded (live import builds one from a ruler's groups, ../export-resources.ts). */
|
|
377
|
+
export function parseRuleFileDocument(doc) {
|
|
378
|
+
const warnings = [];
|
|
379
|
+
return { file: parseRuleFile(doc, warnings), warnings };
|
|
380
|
+
}
|
|
381
|
+
/** An `alertmanager.yml` already loaded (live import reads one from Alertmanager's API, ../export-resources.ts). */
|
|
382
|
+
export function parseAlertmanagerDocument(doc) {
|
|
383
|
+
const warnings = [];
|
|
384
|
+
return { config: parseAlertmanager(doc, warnings), warnings };
|
|
385
|
+
}
|
|
386
|
+
/** Parse a rule file or `alertmanager.yml`, telling them apart by shape. */
|
|
387
|
+
export function parsePrometheusYaml(content) {
|
|
388
|
+
const warnings = [];
|
|
389
|
+
const doc = loadPrometheusYaml(content);
|
|
390
|
+
if (!isPlainObject(doc)) {
|
|
391
|
+
throw new Error("a Prometheus rule file, alertmanager.yml or prometheus.yml is a YAML mapping; this document is not one");
|
|
392
|
+
}
|
|
393
|
+
if (looksLikeRuleFile(doc))
|
|
394
|
+
return { kind: "rules", file: parseRuleFile(doc, warnings), warnings };
|
|
395
|
+
if (looksLikeAlertmanagerConfig(doc))
|
|
396
|
+
return { kind: "alertmanager", config: parseAlertmanager(doc, warnings), warnings };
|
|
397
|
+
if (looksLikePrometheusConfig(doc))
|
|
398
|
+
return { kind: "prometheus", config: parsePrometheusConfig(doc, warnings), warnings };
|
|
399
|
+
if ("groups" in doc) {
|
|
400
|
+
// A rule file with a malformed group still imports; the bad parts are named.
|
|
401
|
+
return { kind: "rules", file: parseRuleFile(doc, warnings), warnings };
|
|
402
|
+
}
|
|
403
|
+
throw new Error("this YAML is not a Prometheus rule file (groups: of named rule lists), an alertmanager.yml (route: or receivers:) or a prometheus.yml (scrape_configs: and the like)");
|
|
404
|
+
}
|
|
405
|
+
/** The rule file and `alertmanager.yml` parser `chant import` runs. */
|
|
406
|
+
export class PrometheusParser {
|
|
407
|
+
parse(content) {
|
|
408
|
+
const parsed = parsePrometheusYaml(content);
|
|
409
|
+
const resource = parsed.kind === "prometheus"
|
|
410
|
+
? {
|
|
411
|
+
logicalId: "prometheus",
|
|
412
|
+
type: PROMETHEUS_RESOURCE_TYPE,
|
|
413
|
+
properties: { config: parsed.config },
|
|
414
|
+
}
|
|
415
|
+
: parsed.kind === "rules"
|
|
416
|
+
? {
|
|
417
|
+
logicalId: "ruleFile",
|
|
418
|
+
type: RULE_FILE_RESOURCE_TYPE,
|
|
419
|
+
properties: { file: parsed.file },
|
|
420
|
+
}
|
|
421
|
+
: {
|
|
422
|
+
logicalId: "alertmanager",
|
|
423
|
+
type: ALERTMANAGER_RESOURCE_TYPE,
|
|
424
|
+
properties: { config: parsed.config },
|
|
425
|
+
};
|
|
426
|
+
return { resources: [resource], parameters: [], warnings: parsed.warnings };
|
|
427
|
+
}
|
|
428
|
+
}
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Recognise a rule group that `Slo()` built, so the importer can write the
|
|
3
|
+
* `Slo` declaration instead of its forty-odd lines of rules.
|
|
4
|
+
*
|
|
5
|
+
* The group's name, recorded series, objective and burn-rate alerts give
|
|
6
|
+
* back candidate props; the candidate is accepted only when `Slo()` builds
|
|
7
|
+
* it to the same group, rule for rule. Anything else (a hand-edited
|
|
8
|
+
* threshold, an extra rule, a different description) fails that check and
|
|
9
|
+
* the group is imported as a plain `RuleGroup`.
|
|
10
|
+
*/
|
|
11
|
+
import { Slo, DEFAULT_BURN_RATES, SLO_WINDOW_PLACEHOLDER } from "../composites/slo.js";
|
|
12
|
+
import { ruleGroupConfig } from "../rules.js";
|
|
13
|
+
import { durationMs } from "../duration.js";
|
|
14
|
+
import { isAlertingRuleConfig, isRecordingRuleConfig } from "../model.js";
|
|
15
|
+
const RATIO_PREFIX = "slo:sli_error:ratio_rate";
|
|
16
|
+
const OBJECTIVE = "slo:objective:ratio";
|
|
17
|
+
const BUDGET = "slo:error_budget:remaining";
|
|
18
|
+
const DEFAULT_ALERT_NAME = "ErrorBudgetBurn";
|
|
19
|
+
const PAIR_LABELS = new Set(["slo", "severity", "long_window", "short_window"]);
|
|
20
|
+
const DESCRIPTION_TAIL = "The error ratio over both the last ";
|
|
21
|
+
/** Objects with their keys sorted, so two configs compare equal whatever order their keys were written in. */
|
|
22
|
+
function canonical(v) {
|
|
23
|
+
if (Array.isArray(v))
|
|
24
|
+
return v.map(canonical);
|
|
25
|
+
if (typeof v === "object" && v !== null) {
|
|
26
|
+
return Object.fromEntries(Object.keys(v)
|
|
27
|
+
.sort()
|
|
28
|
+
.map((k) => [k, canonical(v[k])]));
|
|
29
|
+
}
|
|
30
|
+
return v;
|
|
31
|
+
}
|
|
32
|
+
function same(a, b) {
|
|
33
|
+
return JSON.stringify(canonical(a)) === JSON.stringify(canonical(b));
|
|
34
|
+
}
|
|
35
|
+
/** The SLI expressions a recorded error ratio was built from, with the window put back as `{{window}}`. */
|
|
36
|
+
function sliFrom(expr, window) {
|
|
37
|
+
const unwindow = (s) => s.split(`[${window}]`).join(`[${SLO_WINDOW_PLACEHOLDER}]`);
|
|
38
|
+
// Since #3548 a window with no events records nothing: `((E) or 0 * ((T) > 0)) / ((T) > 0)`, and the good/total form
|
|
39
|
+
// as `1 - (((G) or 0 * ((T) > 0)) / ((T) > 0))`.
|
|
40
|
+
const idle = (head, divide, tail, key) => {
|
|
41
|
+
if (!expr.startsWith(head) || !expr.endsWith(tail))
|
|
42
|
+
return undefined;
|
|
43
|
+
const body = expr.slice(head.length, expr.length - tail.length);
|
|
44
|
+
const orAt = body.indexOf(") or 0 * ((");
|
|
45
|
+
const divAt = body.indexOf(`) > 0))${divide}((`);
|
|
46
|
+
if (orAt === -1 || divAt < orAt)
|
|
47
|
+
return undefined;
|
|
48
|
+
const total = body.slice(divAt + `) > 0))${divide}((`.length);
|
|
49
|
+
return { [key]: unwindow(body.slice(0, orAt)), total: unwindow(total) };
|
|
50
|
+
};
|
|
51
|
+
const idleErrors = idle("((", "\n/\n", ") > 0)", "errors");
|
|
52
|
+
if (idleErrors)
|
|
53
|
+
return idleErrors;
|
|
54
|
+
const idleGood = idle("1 - (\n ((", "\n /\n ", ") > 0)\n)", "good");
|
|
55
|
+
if (idleGood)
|
|
56
|
+
return idleGood;
|
|
57
|
+
const goodHead = "1 - (\n (";
|
|
58
|
+
const goodTail = ")\n)";
|
|
59
|
+
if (expr.startsWith(goodHead) && expr.endsWith(goodTail)) {
|
|
60
|
+
const inner = expr.slice(goodHead.length, -goodTail.length);
|
|
61
|
+
const at = inner.indexOf(")\n /\n (");
|
|
62
|
+
if (at === -1)
|
|
63
|
+
return undefined;
|
|
64
|
+
return { good: unwindow(inner.slice(0, at)), total: unwindow(inner.slice(at + ")\n /\n (".length)) };
|
|
65
|
+
}
|
|
66
|
+
if (expr.startsWith("(") && expr.endsWith(")")) {
|
|
67
|
+
const inner = expr.slice(1, -1);
|
|
68
|
+
const at = inner.indexOf(")\n/\n(");
|
|
69
|
+
if (at === -1)
|
|
70
|
+
return undefined;
|
|
71
|
+
return { errors: unwindow(inner.slice(0, at)), total: unwindow(inner.slice(at + ")\n/\n(".length)) };
|
|
72
|
+
}
|
|
73
|
+
return undefined;
|
|
74
|
+
}
|
|
75
|
+
function extras(set, skip) {
|
|
76
|
+
const out = {};
|
|
77
|
+
for (const [k, v] of Object.entries(set ?? {}))
|
|
78
|
+
if (!skip(k))
|
|
79
|
+
out[k] = v;
|
|
80
|
+
return Object.keys(out).length > 0 ? out : undefined;
|
|
81
|
+
}
|
|
82
|
+
function isDefaultPairs(tier, pairs, factors, windowMs, sloWindow) {
|
|
83
|
+
const defaults = DEFAULT_BURN_RATES[tier];
|
|
84
|
+
if (defaults.length !== pairs.length)
|
|
85
|
+
return false;
|
|
86
|
+
return defaults.every((d, i) => {
|
|
87
|
+
const p = pairs[i];
|
|
88
|
+
const factor = Number(((d.budgetConsumed * windowMs(sloWindow)) / windowMs(d.long)).toPrecision(6));
|
|
89
|
+
return p.long === d.long && p.short === d.short && factor === factors[i];
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
/** The `Slo` props that build exactly `group`, or `undefined` when no `Slo()` call does. */
|
|
93
|
+
export function recognizeSlo(group) {
|
|
94
|
+
if (!group.name.startsWith("slo-") || group.query_offset !== undefined || group.limit !== undefined)
|
|
95
|
+
return undefined;
|
|
96
|
+
const name = group.name.slice("slo-".length);
|
|
97
|
+
const records = group.rules.filter(isRecordingRuleConfig);
|
|
98
|
+
const alerts = group.rules.filter(isAlertingRuleConfig);
|
|
99
|
+
if (records.length + alerts.length !== group.rules.length)
|
|
100
|
+
return undefined;
|
|
101
|
+
const objectiveAt = records.findIndex((r) => r.record === OBJECTIVE);
|
|
102
|
+
if (objectiveAt < 1 || records[objectiveAt + 1]?.record !== BUDGET)
|
|
103
|
+
return undefined;
|
|
104
|
+
const objective = Number(/^vector\((.+)\)$/.exec(records[objectiveAt].expr)?.[1]);
|
|
105
|
+
if (!Number.isFinite(objective))
|
|
106
|
+
return undefined;
|
|
107
|
+
const windowRecord = records[objectiveAt - 1].record;
|
|
108
|
+
if (!windowRecord.startsWith(RATIO_PREFIX))
|
|
109
|
+
return undefined;
|
|
110
|
+
const window = windowRecord.slice(RATIO_PREFIX.length);
|
|
111
|
+
// Candidate SLIs: from each alert window's ratio, or from the whole-window
|
|
112
|
+
// ratio when there are no alerts.
|
|
113
|
+
const ratioRecords = records.slice(0, objectiveAt - 1);
|
|
114
|
+
const sources = ratioRecords.length > 0 ? ratioRecords : [records[objectiveAt - 1]];
|
|
115
|
+
const slis = sources
|
|
116
|
+
.map((r) => (r.record.startsWith(RATIO_PREFIX) ? sliFrom(r.expr, r.record.slice(RATIO_PREFIX.length)) : undefined))
|
|
117
|
+
.filter((s) => s !== undefined);
|
|
118
|
+
if (slis.length === 0)
|
|
119
|
+
return undefined;
|
|
120
|
+
// Alert tiers: `page` and `ticket` by severity, or custom severities in the order they appear.
|
|
121
|
+
const alertName = alerts[0]?.alert ?? DEFAULT_ALERT_NAME;
|
|
122
|
+
const bySeverity = new Map();
|
|
123
|
+
for (const a of alerts) {
|
|
124
|
+
const severity = a.labels?.severity;
|
|
125
|
+
if (severity === undefined || a.alert !== alertName)
|
|
126
|
+
return undefined;
|
|
127
|
+
bySeverity.set(severity, [...(bySeverity.get(severity) ?? []), a]);
|
|
128
|
+
}
|
|
129
|
+
if (bySeverity.size > 2)
|
|
130
|
+
return undefined;
|
|
131
|
+
const severities = [...bySeverity.keys()];
|
|
132
|
+
const tierOf = new Map();
|
|
133
|
+
for (const s of severities)
|
|
134
|
+
if (s === "page" || s === "ticket")
|
|
135
|
+
tierOf.set(s, s);
|
|
136
|
+
for (const s of severities) {
|
|
137
|
+
if (tierOf.has(s))
|
|
138
|
+
continue;
|
|
139
|
+
const free = ["page", "ticket"].find((t) => ![...tierOf.values()].includes(t));
|
|
140
|
+
if (!free)
|
|
141
|
+
return undefined;
|
|
142
|
+
tierOf.set(s, free);
|
|
143
|
+
}
|
|
144
|
+
let description;
|
|
145
|
+
const alerting = {};
|
|
146
|
+
for (const tier of ["page", "ticket"]) {
|
|
147
|
+
const severity = [...tierOf].find(([, t]) => t === tier)?.[0];
|
|
148
|
+
if (severity === undefined) {
|
|
149
|
+
alerting[tier] = false;
|
|
150
|
+
continue;
|
|
151
|
+
}
|
|
152
|
+
const tierAlerts = bySeverity.get(severity);
|
|
153
|
+
const first = tierAlerts[0];
|
|
154
|
+
const pairs = [];
|
|
155
|
+
const factors = [];
|
|
156
|
+
for (const a of tierAlerts) {
|
|
157
|
+
const long = a.labels?.long_window;
|
|
158
|
+
const short = a.labels?.short_window;
|
|
159
|
+
const factor = Number(/> \(([^ ]+) \* /.exec(a.expr)?.[1]);
|
|
160
|
+
if (long === undefined || short === undefined || !Number.isFinite(factor))
|
|
161
|
+
return undefined;
|
|
162
|
+
pairs.push({ long, short, factor });
|
|
163
|
+
factors.push(factor);
|
|
164
|
+
}
|
|
165
|
+
const t = {};
|
|
166
|
+
t.burnRates = isDefaultPairs(tier, pairs, factors, (w) => durationMs(w) ?? 0, window) ? "default" : pairs;
|
|
167
|
+
if (severity !== tier)
|
|
168
|
+
t.severity = severity;
|
|
169
|
+
if (first.for !== undefined)
|
|
170
|
+
t.for = first.for;
|
|
171
|
+
const labels = extras(first.labels, (k) => PAIR_LABELS.has(k));
|
|
172
|
+
if (labels)
|
|
173
|
+
t.labels = labels;
|
|
174
|
+
const annotations = extras(first.annotations, (k) => k === "summary" || k === "description");
|
|
175
|
+
if (annotations)
|
|
176
|
+
t.annotations = annotations;
|
|
177
|
+
const desc = first.annotations?.description ?? "";
|
|
178
|
+
const at = desc.indexOf(DESCRIPTION_TAIL);
|
|
179
|
+
if (at > 0 && description === undefined)
|
|
180
|
+
description = desc.slice(0, at).trimEnd();
|
|
181
|
+
const onlyDefault = t.burnRates === "default" && Object.keys(t).length === 1;
|
|
182
|
+
if (!onlyDefault)
|
|
183
|
+
alerting[tier] = t;
|
|
184
|
+
}
|
|
185
|
+
if (alertName !== DEFAULT_ALERT_NAME)
|
|
186
|
+
alerting.alertName = alertName;
|
|
187
|
+
for (const sli of slis) {
|
|
188
|
+
const props = {
|
|
189
|
+
name,
|
|
190
|
+
objective,
|
|
191
|
+
window,
|
|
192
|
+
...(description ? { description } : {}),
|
|
193
|
+
sli,
|
|
194
|
+
...(Object.keys(alerting).length > 0 ? { alerting } : {}),
|
|
195
|
+
...(group.labels ? { labels: group.labels } : {}),
|
|
196
|
+
...(group.interval !== undefined ? { interval: group.interval } : {}),
|
|
197
|
+
};
|
|
198
|
+
try {
|
|
199
|
+
if (same(ruleGroupConfig(Slo(props).rules), ruleGroupConfig(group)))
|
|
200
|
+
return props;
|
|
201
|
+
}
|
|
202
|
+
catch {
|
|
203
|
+
// Not buildable from these props; try the next candidate.
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
return undefined;
|
|
207
|
+
}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
// Prometheus lexicon.
|
|
2
|
+
// Plugin and serializer
|
|
3
|
+
export { prometheusPlugin } from "./plugin.js";
|
|
4
|
+
export { prometheusSerializer, ALERTMANAGER_FILE, PROMETHEUS_FILE } from "./serializer.js";
|
|
5
|
+
// Rule groups
|
|
6
|
+
export { RuleGroup, RULE_GROUP_TYPE, isRuleGroup, ruleGroupConfig, ruleConfig, } from "./rules.js";
|
|
7
|
+
// Alertmanager
|
|
8
|
+
export { Route, Receiver, InhibitRule, TimeInterval, AlertmanagerSettings, ROUTE_TYPE, RECEIVER_TYPE, INHIBIT_RULE_TYPE, TIME_INTERVAL_TYPE, SETTINGS_TYPE, isRoute, isReceiver, isInhibitRule, isTimeInterval, isAlertmanagerSettings, isAlertmanagerEntity, } from "./alertmanager.js";
|
|
9
|
+
// prometheus.yml
|
|
10
|
+
export { PrometheusConfig, ScrapeConfig, PROMETHEUS_CONFIG_TYPE, SCRAPE_CONFIG_TYPE, isPrometheusConfig, isScrapeConfig, isPrometheusConfigEntity, } from "./prometheus-config.js";
|
|
11
|
+
// The plain-data model, building, YAML and checks
|
|
12
|
+
export * from "./model.js";
|
|
13
|
+
export { buildRuleFile, buildAlertmanagerConfig, buildPrometheusConfig, ruleFileYaml, alertmanagerYaml, prometheusConfigYaml, emitYaml, } from "./build.js";
|
|
14
|
+
export { isValidDuration, durationMs, formatDuration } from "./duration.js";
|
|
15
|
+
export { parseMatchers, matcherMatches, matcher } from "./matchers.js";
|
|
16
|
+
export { checkPromql, PROMQL_GRAMMAR } from "./promql.js";
|
|
17
|
+
export { validateRuleFile, validateRunbookUrls, validateAlertmanagerConfig, validateSeverityRouting, alertSeverities, } from "./validate-config.js";
|
|
18
|
+
export { PROMETHEUS_PIN } from "./pin.js";
|
|
19
|
+
// promtool and amtool, when installed
|
|
20
|
+
export { promtoolCheckRules, promtoolCheckConfig, promtoolTestRules, amtoolCheckConfig, hasTool } from "./tools.js";
|
|
21
|
+
// Composites
|
|
22
|
+
export { Slo, sloMetrics, sloPropsProblem, sliExprProblem, eventCount, DEFAULT_BURN_RATES, SLO_WINDOW_PLACEHOLDER, GenAiRules, genAiRuleMetrics, RedAlerts, redAlertRules, RED_ALERT_NAMES, Watchdog, WATCHDOG_URL_FILE, AlertRouting, ALERT_ROUTING_LEVELS, } from "./composites/index.js";
|
|
23
|
+
// Config namespace (#3371): `prometheus.profiles.<env>` in chant.config.ts, the
|
|
24
|
+
// ruler and Alertmanager each environment observes and imports from. The API
|
|
25
|
+
// clients are `@intentius/chant-lexicon-prometheus/api`.
|
|
26
|
+
export { prometheusConfigSchema, resolveRulerTarget, resolveAlertmanagerTarget, declaredNamespaces, namespaceOfGroup } from "./config.js";
|
|
27
|
+
// Op steps (#3369). The activities resolve from
|
|
28
|
+
// `@intentius/chant-lexicon-prometheus/op/activities`; the promtool and amtool
|
|
29
|
+
// check builders share their names with the functions above, so they are at
|
|
30
|
+
// `@intentius/chant-lexicon-prometheus/op/builders`.
|
|
31
|
+
export { amtoolRoutesTest, alertmanagerSilence, alertmanagerUnsilence, rulesLoadedObserve, ruleAudit } from "./op/builders.js";
|
|
32
|
+
export { RuleAuditOp } from "./op/audit-op.js";
|
|
33
|
+
export { sloRuleTests } from "./op/slo-rule-tests.js";
|