@intentius/chant-lexicon-prometheus 0.96.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 +58 -0
- package/dist/alertmanager.d.ts +81 -0
- package/dist/alertmanager.d.ts.map +1 -0
- package/dist/build.d.ts +33 -0
- package/dist/build.d.ts.map +1 -0
- package/dist/catalog.d.ts +17 -0
- package/dist/catalog.d.ts.map +1 -0
- package/dist/codegen/docs-cli.d.ts +3 -0
- package/dist/codegen/docs-cli.d.ts.map +1 -0
- package/dist/codegen/docs.d.ts +9 -0
- package/dist/codegen/docs.d.ts.map +1 -0
- package/dist/codegen/generate-cli.d.ts +3 -0
- package/dist/codegen/generate-cli.d.ts.map +1 -0
- package/dist/codegen/generate.d.ts +19 -0
- package/dist/codegen/generate.d.ts.map +1 -0
- package/dist/codegen/package.d.ts +7 -0
- package/dist/codegen/package.d.ts.map +1 -0
- package/dist/composites/catalog.d.ts +3 -0
- package/dist/composites/catalog.d.ts.map +1 -0
- package/dist/composites/index.d.ts +6 -0
- package/dist/composites/index.d.ts.map +1 -0
- package/dist/composites/slo.d.ts +205 -0
- package/dist/composites/slo.d.ts.map +1 -0
- package/dist/detect.d.ts +2 -0
- package/dist/detect.d.ts.map +1 -0
- package/dist/duration.d.ts +16 -0
- package/dist/duration.d.ts.map +1 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/init-templates.d.ts +13 -0
- package/dist/init-templates.d.ts.map +1 -0
- package/dist/integrity.json +33 -0
- package/dist/lint/audit-catalog.d.ts +13 -0
- package/dist/lint/audit-catalog.d.ts.map +1 -0
- package/dist/lint/post-synth/index.d.ts +3 -0
- package/dist/lint/post-synth/index.d.ts.map +1 -0
- package/dist/lint/post-synth/prom-helpers.d.ts +33 -0
- package/dist/lint/post-synth/prom-helpers.d.ts.map +1 -0
- package/dist/lint/post-synth/prom101.d.ts +8 -0
- package/dist/lint/post-synth/prom101.d.ts.map +1 -0
- package/dist/lint/post-synth/prom102.d.ts +8 -0
- package/dist/lint/post-synth/prom102.d.ts.map +1 -0
- package/dist/lint/post-synth/prom103.d.ts +8 -0
- package/dist/lint/post-synth/prom103.d.ts.map +1 -0
- package/dist/lint/post-synth/prom104.d.ts +8 -0
- package/dist/lint/post-synth/prom104.d.ts.map +1 -0
- package/dist/lint/post-synth/prom105.d.ts +8 -0
- package/dist/lint/post-synth/prom105.d.ts.map +1 -0
- package/dist/lint/post-synth/prom106.d.ts +8 -0
- package/dist/lint/post-synth/prom106.d.ts.map +1 -0
- package/dist/lint/post-synth/prom107.d.ts +8 -0
- package/dist/lint/post-synth/prom107.d.ts.map +1 -0
- package/dist/lint/post-synth/prom201.d.ts +8 -0
- package/dist/lint/post-synth/prom201.d.ts.map +1 -0
- package/dist/lint/post-synth/prom202.d.ts +8 -0
- package/dist/lint/post-synth/prom202.d.ts.map +1 -0
- package/dist/lint/post-synth/prom203.d.ts +8 -0
- package/dist/lint/post-synth/prom203.d.ts.map +1 -0
- package/dist/lint/post-synth/prom204.d.ts +8 -0
- package/dist/lint/post-synth/prom204.d.ts.map +1 -0
- package/dist/lint/post-synth/prom205.d.ts +8 -0
- package/dist/lint/post-synth/prom205.d.ts.map +1 -0
- package/dist/lint/post-synth/prom206.d.ts +8 -0
- package/dist/lint/post-synth/prom206.d.ts.map +1 -0
- package/dist/lint/post-synth/prom207.d.ts +8 -0
- package/dist/lint/post-synth/prom207.d.ts.map +1 -0
- package/dist/lint/post-synth/prom208.d.ts +8 -0
- package/dist/lint/post-synth/prom208.d.ts.map +1 -0
- package/dist/lint/post-synth/prom209.d.ts +8 -0
- package/dist/lint/post-synth/prom209.d.ts.map +1 -0
- package/dist/lint/rules/index.d.ts +7 -0
- package/dist/lint/rules/index.d.ts.map +1 -0
- package/dist/lint/rules/literal-credential.d.ts +18 -0
- package/dist/lint/rules/literal-credential.d.ts.map +1 -0
- package/dist/lint/rules/prom-ast.d.ts +14 -0
- package/dist/lint/rules/prom-ast.d.ts.map +1 -0
- package/dist/lint/rules/promql-literal.d.ts +11 -0
- package/dist/lint/rules/promql-literal.d.ts.map +1 -0
- package/dist/lint/rules/slo-literal.d.ts +13 -0
- package/dist/lint/rules/slo-literal.d.ts.map +1 -0
- package/dist/lsp/completions.d.ts +4 -0
- package/dist/lsp/completions.d.ts.map +1 -0
- package/dist/lsp/hover.d.ts +4 -0
- package/dist/lsp/hover.d.ts.map +1 -0
- package/dist/manifest.json +8 -0
- package/dist/matchers.d.ts +29 -0
- package/dist/matchers.d.ts.map +1 -0
- package/dist/meta.json +32 -0
- package/dist/model.d.ts +262 -0
- package/dist/model.d.ts.map +1 -0
- package/dist/okf/index.md +34 -0
- package/dist/okf/rules/PROM001.md +16 -0
- package/dist/okf/rules/PROM002.md +15 -0
- package/dist/okf/rules/PROM003.md +11 -0
- package/dist/okf/rules/PROM101.md +11 -0
- package/dist/okf/rules/PROM102.md +11 -0
- package/dist/okf/rules/PROM103.md +11 -0
- package/dist/okf/rules/PROM104.md +11 -0
- package/dist/okf/rules/PROM105.md +11 -0
- package/dist/okf/rules/PROM106.md +11 -0
- package/dist/okf/rules/PROM107.md +11 -0
- package/dist/okf/rules/PROM201.md +11 -0
- package/dist/okf/rules/PROM202.md +11 -0
- package/dist/okf/rules/PROM203.md +11 -0
- package/dist/okf/rules/PROM204.md +11 -0
- package/dist/okf/rules/PROM205.md +11 -0
- package/dist/okf/rules/PROM206.md +11 -0
- package/dist/okf/rules/PROM207.md +11 -0
- package/dist/okf/rules/PROM208.md +11 -0
- package/dist/okf/rules/PROM209.md +11 -0
- package/dist/okf/types/AlertmanagerSettings.md +13 -0
- package/dist/okf/types/InhibitRule.md +9 -0
- package/dist/okf/types/Receiver.md +13 -0
- package/dist/okf/types/Route.md +9 -0
- package/dist/okf/types/RuleGroup.md +13 -0
- package/dist/okf/types/TimeInterval.md +9 -0
- package/dist/package-cli.d.ts +3 -0
- package/dist/package-cli.d.ts.map +1 -0
- package/dist/pin.d.ts +20 -0
- package/dist/pin.d.ts.map +1 -0
- package/dist/plugin.d.ts +10 -0
- package/dist/plugin.d.ts.map +1 -0
- package/dist/promql.d.ts +25 -0
- package/dist/promql.d.ts.map +1 -0
- package/dist/rule-eval.d.ts +67 -0
- package/dist/rule-eval.d.ts.map +1 -0
- package/dist/rules/literal-credential.ts +84 -0
- package/dist/rules/prom-ast.ts +30 -0
- package/dist/rules/prom-helpers.ts +89 -0
- package/dist/rules/prom101.ts +17 -0
- package/dist/rules/prom102.ts +17 -0
- package/dist/rules/prom103.ts +17 -0
- package/dist/rules/prom104.ts +17 -0
- package/dist/rules/prom105.ts +17 -0
- package/dist/rules/prom106.ts +17 -0
- package/dist/rules/prom107.ts +17 -0
- package/dist/rules/prom201.ts +17 -0
- package/dist/rules/prom202.ts +17 -0
- package/dist/rules/prom203.ts +17 -0
- package/dist/rules/prom204.ts +17 -0
- package/dist/rules/prom205.ts +17 -0
- package/dist/rules/prom206.ts +17 -0
- package/dist/rules/prom207.ts +17 -0
- package/dist/rules/prom208.ts +17 -0
- package/dist/rules/prom209.ts +17 -0
- package/dist/rules/promql-literal.ts +55 -0
- package/dist/rules/slo-literal.ts +81 -0
- package/dist/rules.d.ts +59 -0
- package/dist/rules.d.ts.map +1 -0
- package/dist/serializer.d.ts +27 -0
- package/dist/serializer.d.ts.map +1 -0
- package/dist/skill-defs.d.ts +3 -0
- package/dist/skill-defs.d.ts.map +1 -0
- package/dist/skills/chant-prometheus-alertmanager.md +58 -0
- package/dist/skills/chant-prometheus-kubernetes.md +48 -0
- package/dist/skills/chant-prometheus.md +91 -0
- package/dist/tools.d.ts +30 -0
- package/dist/tools.d.ts.map +1 -0
- package/dist/types/index.d.ts +3 -0
- package/dist/validate-cli.d.ts +3 -0
- package/dist/validate-cli.d.ts.map +1 -0
- package/dist/validate-config.d.ts +35 -0
- package/dist/validate-config.d.ts.map +1 -0
- package/dist/validate.d.ts +11 -0
- package/dist/validate.d.ts.map +1 -0
- package/package.json +77 -0
- package/src/alertmanager.ts +143 -0
- package/src/build.ts +199 -0
- package/src/catalog.ts +65 -0
- package/src/codegen/docs-cli.ts +4 -0
- package/src/codegen/docs.ts +104 -0
- package/src/codegen/generate-cli.ts +8 -0
- package/src/codegen/generate.ts +49 -0
- package/src/codegen/package.ts +41 -0
- package/src/composites/catalog.test.ts +37 -0
- package/src/composites/catalog.ts +65 -0
- package/src/composites/composites.test.ts +256 -0
- package/src/composites/index.ts +23 -0
- package/src/composites/slo-burn.test.ts +289 -0
- package/src/composites/slo.ts +471 -0
- package/src/detect.ts +12 -0
- package/src/duration.ts +51 -0
- package/src/generated/lexicon-prometheus.json +32 -0
- package/src/index.ts +97 -0
- package/src/init-templates.test.ts +29 -0
- package/src/init-templates.ts +76 -0
- package/src/lint/audit-catalog.ts +123 -0
- package/src/lint/post-synth/index.ts +37 -0
- package/src/lint/post-synth/post-synth.test.ts +256 -0
- package/src/lint/post-synth/prom-helpers.ts +89 -0
- package/src/lint/post-synth/prom101.ts +17 -0
- package/src/lint/post-synth/prom102.ts +17 -0
- package/src/lint/post-synth/prom103.ts +17 -0
- package/src/lint/post-synth/prom104.ts +17 -0
- package/src/lint/post-synth/prom105.ts +17 -0
- package/src/lint/post-synth/prom106.ts +17 -0
- package/src/lint/post-synth/prom107.ts +17 -0
- package/src/lint/post-synth/prom201.ts +17 -0
- package/src/lint/post-synth/prom202.ts +17 -0
- package/src/lint/post-synth/prom203.ts +17 -0
- package/src/lint/post-synth/prom204.ts +17 -0
- package/src/lint/post-synth/prom205.ts +17 -0
- package/src/lint/post-synth/prom206.ts +17 -0
- package/src/lint/post-synth/prom207.ts +17 -0
- package/src/lint/post-synth/prom208.ts +17 -0
- package/src/lint/post-synth/prom209.ts +17 -0
- package/src/lint/rules/index.ts +11 -0
- package/src/lint/rules/literal-credential.ts +84 -0
- package/src/lint/rules/prom-ast.ts +30 -0
- package/src/lint/rules/promql-literal.ts +55 -0
- package/src/lint/rules/rules.test.ts +114 -0
- package/src/lint/rules/slo-literal.ts +81 -0
- package/src/lsp/completions.test.ts +23 -0
- package/src/lsp/completions.ts +15 -0
- package/src/lsp/hover.test.ts +27 -0
- package/src/lsp/hover.ts +26 -0
- package/src/matchers.ts +132 -0
- package/src/model.test.ts +104 -0
- package/src/model.ts +290 -0
- package/src/package-cli.ts +17 -0
- package/src/pin.ts +13 -0
- package/src/plugin.test.ts +73 -0
- package/src/plugin.ts +129 -0
- package/src/promql.ts +46 -0
- package/src/rule-eval.ts +397 -0
- package/src/rules.ts +127 -0
- package/src/serializer.test.ts +179 -0
- package/src/serializer.ts +55 -0
- package/src/skill-defs.ts +43 -0
- package/src/skills/chant-prometheus-alertmanager.md +58 -0
- package/src/skills/chant-prometheus-kubernetes.md +48 -0
- package/src/skills/chant-prometheus.md +91 -0
- package/src/tools.test.ts +119 -0
- package/src/tools.ts +70 -0
- package/src/validate-cli.ts +7 -0
- package/src/validate-config.ts +400 -0
- package/src/validate.ts +66 -0
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PROM104: A rule expression is not valid PromQL
|
|
3
|
+
*
|
|
4
|
+
* Every expr is parsed with the PromQL grammar the Prometheus project publishes. A syntax error fails the rule file at load time.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
|
|
8
|
+
import { ruleFileDiagnostics } from "./prom-helpers";
|
|
9
|
+
|
|
10
|
+
export const prom104: PostSynthCheck = {
|
|
11
|
+
id: "PROM104",
|
|
12
|
+
description: "A rule expression is not valid PromQL",
|
|
13
|
+
|
|
14
|
+
check(ctx: PostSynthContext): PostSynthDiagnostic[] {
|
|
15
|
+
return ruleFileDiagnostics(ctx, "PROM104");
|
|
16
|
+
},
|
|
17
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PROM105: A rule or group is malformed
|
|
3
|
+
*
|
|
4
|
+
* A rule sets exactly one of record and alert, has a non-empty name, and a recording rule takes no for, keep_firing_for or annotations. A group has a name.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
|
|
8
|
+
import { ruleFileDiagnostics } from "./prom-helpers";
|
|
9
|
+
|
|
10
|
+
export const prom105: PostSynthCheck = {
|
|
11
|
+
id: "PROM105",
|
|
12
|
+
description: "A rule or group is malformed",
|
|
13
|
+
|
|
14
|
+
check(ctx: PostSynthContext): PostSynthDiagnostic[] {
|
|
15
|
+
return ruleFileDiagnostics(ctx, "PROM105");
|
|
16
|
+
},
|
|
17
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PROM106: An alerting rule has no severity label
|
|
3
|
+
*
|
|
4
|
+
* Alertmanager routes on labels, and severity is the one every route in this lexicon's examples and checks keys on. Without it the alert can only be routed by name.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
|
|
8
|
+
import { ruleFileDiagnostics } from "./prom-helpers";
|
|
9
|
+
|
|
10
|
+
export const prom106: PostSynthCheck = {
|
|
11
|
+
id: "PROM106",
|
|
12
|
+
description: "An alerting rule has no severity label",
|
|
13
|
+
|
|
14
|
+
check(ctx: PostSynthContext): PostSynthDiagnostic[] {
|
|
15
|
+
return ruleFileDiagnostics(ctx, "PROM106");
|
|
16
|
+
},
|
|
17
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PROM107: An alerting rule has no summary or description annotation
|
|
3
|
+
*
|
|
4
|
+
* A notification carries only the alert's labels unless an annotation says what is wrong.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
|
|
8
|
+
import { ruleFileDiagnostics } from "./prom-helpers";
|
|
9
|
+
|
|
10
|
+
export const prom107: PostSynthCheck = {
|
|
11
|
+
id: "PROM107",
|
|
12
|
+
description: "An alerting rule has no summary or description annotation",
|
|
13
|
+
|
|
14
|
+
check(ctx: PostSynthContext): PostSynthDiagnostic[] {
|
|
15
|
+
return ruleFileDiagnostics(ctx, "PROM107");
|
|
16
|
+
},
|
|
17
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PROM201: A route sends to a receiver that is not declared
|
|
3
|
+
*
|
|
4
|
+
* Alertmanager refuses a config whose routes name an undefined receiver.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
|
|
8
|
+
import { alertmanagerDiagnostics } from "./prom-helpers";
|
|
9
|
+
|
|
10
|
+
export const prom201: PostSynthCheck = {
|
|
11
|
+
id: "PROM201",
|
|
12
|
+
description: "A route sends to a receiver that is not declared",
|
|
13
|
+
|
|
14
|
+
check(ctx: PostSynthContext): PostSynthDiagnostic[] {
|
|
15
|
+
return alertmanagerDiagnostics(ctx, "PROM201");
|
|
16
|
+
},
|
|
17
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PROM202: An alert severity is not routed by any route
|
|
3
|
+
*
|
|
4
|
+
* Joins the rule files and the Alertmanager config in one build root. An alert whose severity no route below the root matches falls through to the root's default receiver. Silent when the rules and the Alertmanager config are built in different build roots (chant #1939).
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
|
|
8
|
+
import { routingDiagnostics } from "./prom-helpers";
|
|
9
|
+
|
|
10
|
+
export const prom202: PostSynthCheck = {
|
|
11
|
+
id: "PROM202",
|
|
12
|
+
description: "An alert severity is not routed by any route",
|
|
13
|
+
|
|
14
|
+
check(ctx: PostSynthContext): PostSynthDiagnostic[] {
|
|
15
|
+
return routingDiagnostics(ctx);
|
|
16
|
+
},
|
|
17
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PROM203: Two receivers or two time intervals share a name
|
|
3
|
+
*
|
|
4
|
+
* Alertmanager refuses a config with repeated receiver or time interval names.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
|
|
8
|
+
import { alertmanagerDiagnostics } from "./prom-helpers";
|
|
9
|
+
|
|
10
|
+
export const prom203: PostSynthCheck = {
|
|
11
|
+
id: "PROM203",
|
|
12
|
+
description: "Two receivers or two time intervals share a name",
|
|
13
|
+
|
|
14
|
+
check(ctx: PostSynthContext): PostSynthDiagnostic[] {
|
|
15
|
+
return alertmanagerDiagnostics(ctx, "PROM203");
|
|
16
|
+
},
|
|
17
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PROM204: A route names a time interval that is not declared
|
|
3
|
+
*
|
|
4
|
+
* mute_time_intervals and active_time_intervals must name entries under time_intervals.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
|
|
8
|
+
import { alertmanagerDiagnostics } from "./prom-helpers";
|
|
9
|
+
|
|
10
|
+
export const prom204: PostSynthCheck = {
|
|
11
|
+
id: "PROM204",
|
|
12
|
+
description: "A route names a time interval that is not declared",
|
|
13
|
+
|
|
14
|
+
check(ctx: PostSynthContext): PostSynthDiagnostic[] {
|
|
15
|
+
return alertmanagerDiagnostics(ctx, "PROM204");
|
|
16
|
+
},
|
|
17
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PROM205: The root route is missing, has no receiver, or has matchers
|
|
3
|
+
*
|
|
4
|
+
* The root route is the default for every alert: it must exist, name a receiver and match everything.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
|
|
8
|
+
import { alertmanagerDiagnostics } from "./prom-helpers";
|
|
9
|
+
|
|
10
|
+
export const prom205: PostSynthCheck = {
|
|
11
|
+
id: "PROM205",
|
|
12
|
+
description: "The root route is missing, has no receiver, or has matchers",
|
|
13
|
+
|
|
14
|
+
check(ctx: PostSynthContext): PostSynthDiagnostic[] {
|
|
15
|
+
return alertmanagerDiagnostics(ctx, "PROM205");
|
|
16
|
+
},
|
|
17
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PROM206: A route or inhibit rule matcher does not parse
|
|
3
|
+
*
|
|
4
|
+
* Matchers are label, operator (=, !=, =~, !~) and value, e.g. severity="page".
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
|
|
8
|
+
import { alertmanagerDiagnostics } from "./prom-helpers";
|
|
9
|
+
|
|
10
|
+
export const prom206: PostSynthCheck = {
|
|
11
|
+
id: "PROM206",
|
|
12
|
+
description: "A route or inhibit rule matcher does not parse",
|
|
13
|
+
|
|
14
|
+
check(ctx: PostSynthContext): PostSynthDiagnostic[] {
|
|
15
|
+
return alertmanagerDiagnostics(ctx, "PROM206");
|
|
16
|
+
},
|
|
17
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PROM207: A receiver is declared but no route sends to it
|
|
3
|
+
*
|
|
4
|
+
* An unused receiver usually means a route names the wrong one.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
|
|
8
|
+
import { alertmanagerDiagnostics } from "./prom-helpers";
|
|
9
|
+
|
|
10
|
+
export const prom207: PostSynthCheck = {
|
|
11
|
+
id: "PROM207",
|
|
12
|
+
description: "A receiver is declared but no route sends to it",
|
|
13
|
+
|
|
14
|
+
check(ctx: PostSynthContext): PostSynthDiagnostic[] {
|
|
15
|
+
return alertmanagerDiagnostics(ctx, "PROM207");
|
|
16
|
+
},
|
|
17
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PROM208: An Alertmanager duration is not a duration
|
|
3
|
+
*
|
|
4
|
+
* group_wait, group_interval, repeat_interval, resolve_timeout and webhook timeout take durations such as 30s or 4h.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
|
|
8
|
+
import { alertmanagerDiagnostics } from "./prom-helpers";
|
|
9
|
+
|
|
10
|
+
export const prom208: PostSynthCheck = {
|
|
11
|
+
id: "PROM208",
|
|
12
|
+
description: "An Alertmanager duration is not a duration",
|
|
13
|
+
|
|
14
|
+
check(ctx: PostSynthContext): PostSynthDiagnostic[] {
|
|
15
|
+
return alertmanagerDiagnostics(ctx, "PROM208");
|
|
16
|
+
},
|
|
17
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PROM209: A receiver integration is missing its destination or credential
|
|
3
|
+
*
|
|
4
|
+
* A webhook needs url or url_file, Slack an api_url (or the global one), PagerDuty a routing_key or service_key, and email a to address, a smarthost and a from address (or their global defaults).
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
|
|
8
|
+
import { alertmanagerDiagnostics } from "./prom-helpers";
|
|
9
|
+
|
|
10
|
+
export const prom209: PostSynthCheck = {
|
|
11
|
+
id: "PROM209",
|
|
12
|
+
description: "A receiver integration is missing its destination or credential",
|
|
13
|
+
|
|
14
|
+
check(ctx: PostSynthContext): PostSynthDiagnostic[] {
|
|
15
|
+
return alertmanagerDiagnostics(ctx, "PROM209");
|
|
16
|
+
},
|
|
17
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { LintRule } from "@intentius/chant/lint/rule";
|
|
2
|
+
import { literalCredentialRule } from "./literal-credential";
|
|
3
|
+
import { promqlLiteralRule } from "./promql-literal";
|
|
4
|
+
import { sloLiteralRule } from "./slo-literal";
|
|
5
|
+
|
|
6
|
+
export { literalCredentialRule, SECRET_FIELDS } from "./literal-credential";
|
|
7
|
+
export { promqlLiteralRule } from "./promql-literal";
|
|
8
|
+
export { sloLiteralRule } from "./slo-literal";
|
|
9
|
+
|
|
10
|
+
/** All lint rules provided by this lexicon (imported by plugin.ts's lintRules()). */
|
|
11
|
+
export const rules: LintRule[] = [literalCredentialRule, promqlLiteralRule, sloLiteralRule];
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import * as ts from "typescript";
|
|
2
|
+
import type { LintRule, LintDiagnostic, LintContext } from "@intentius/chant/lint/rule";
|
|
3
|
+
import { calleeName, CREDENTIAL_CLASS, literalText, position, propertyName } from "./prom-ast";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Alertmanager fields that hold a secret, each with a `*_file` sibling that
|
|
7
|
+
* reads it from a mounted file instead.
|
|
8
|
+
*/
|
|
9
|
+
export const SECRET_FIELDS = new Set([
|
|
10
|
+
"api_url", // slack_configs: the incoming-webhook URL is the credential
|
|
11
|
+
"slack_api_url",
|
|
12
|
+
"routing_key",
|
|
13
|
+
"service_key",
|
|
14
|
+
"auth_password",
|
|
15
|
+
"auth_secret",
|
|
16
|
+
"smtp_auth_password",
|
|
17
|
+
"smtp_auth_secret",
|
|
18
|
+
"password",
|
|
19
|
+
"credentials",
|
|
20
|
+
"bearer_token",
|
|
21
|
+
"client_secret",
|
|
22
|
+
]);
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* PROM001: a credential written as a literal in a `Receiver` or
|
|
26
|
+
* `AlertmanagerSettings`.
|
|
27
|
+
*
|
|
28
|
+
* A literal there is a secret in the repository and in the emitted
|
|
29
|
+
* `alertmanager.yml`. Alertmanager does not expand environment variables in
|
|
30
|
+
* its config, so the fix is the field's `*_file` sibling (`api_url_file`,
|
|
31
|
+
* `routing_key_file`, `auth_password_file`, `credentials_file`), pointing at
|
|
32
|
+
* a file mounted from a secret store.
|
|
33
|
+
*/
|
|
34
|
+
export const literalCredentialRule: LintRule = {
|
|
35
|
+
id: "PROM001",
|
|
36
|
+
severity: "error",
|
|
37
|
+
category: "security",
|
|
38
|
+
description: "Alertmanager credential declared as a literal instead of read from a *_file",
|
|
39
|
+
|
|
40
|
+
check(context: LintContext): LintDiagnostic[] {
|
|
41
|
+
const diagnostics: LintDiagnostic[] = [];
|
|
42
|
+
const source = context.sourceFile;
|
|
43
|
+
|
|
44
|
+
const scanValue = (key: string | undefined, init: ts.Expression) => {
|
|
45
|
+
if (ts.isObjectLiteralExpression(init)) {
|
|
46
|
+
scan(init);
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
if (ts.isArrayLiteralExpression(init)) {
|
|
50
|
+
for (const el of init.elements) if (ts.isObjectLiteralExpression(el)) scan(el);
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
if (!key || !SECRET_FIELDS.has(key)) return;
|
|
54
|
+
const text = literalText(init);
|
|
55
|
+
if (text === undefined || text === "") return;
|
|
56
|
+
diagnostics.push({
|
|
57
|
+
ruleId: "PROM001",
|
|
58
|
+
severity: "error",
|
|
59
|
+
message: `\`${key}\` is a literal credential; Alertmanager doesn't expand environment variables, so mount the secret and set \`${key}_file\` instead`,
|
|
60
|
+
file: context.filePath,
|
|
61
|
+
...position(source, init),
|
|
62
|
+
});
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
const scan = (obj: ts.ObjectLiteralExpression) => {
|
|
66
|
+
for (const prop of obj.properties) {
|
|
67
|
+
if (!ts.isPropertyAssignment(prop)) continue;
|
|
68
|
+
scanValue(propertyName(prop), prop.initializer);
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
const visit = (node: ts.Node) => {
|
|
73
|
+
if (ts.isNewExpression(node) || ts.isCallExpression(node)) {
|
|
74
|
+
const name = calleeName(node);
|
|
75
|
+
const arg = node.arguments?.[0];
|
|
76
|
+
if (name && CREDENTIAL_CLASS.test(name) && arg && ts.isObjectLiteralExpression(arg)) scan(arg);
|
|
77
|
+
}
|
|
78
|
+
ts.forEachChild(node, visit);
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
visit(source);
|
|
82
|
+
return diagnostics;
|
|
83
|
+
},
|
|
84
|
+
};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import * as ts from "typescript";
|
|
2
|
+
|
|
3
|
+
/** The constructor name of a `new X(...)` or `X(...)` expression, if it is a plain or dotted name. */
|
|
4
|
+
export function calleeName(node: ts.CallExpression | ts.NewExpression): string | undefined {
|
|
5
|
+
const callee = node.expression;
|
|
6
|
+
if (ts.isIdentifier(callee)) return callee.text;
|
|
7
|
+
if (ts.isPropertyAccessExpression(callee)) return callee.name.text;
|
|
8
|
+
return undefined;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/** The classes whose props hold Alertmanager credentials. */
|
|
12
|
+
export const CREDENTIAL_CLASS = /^(Receiver|AlertmanagerSettings)$/;
|
|
13
|
+
|
|
14
|
+
/** A property's key as text, for identifier and string-literal keys. */
|
|
15
|
+
export function propertyName(prop: ts.ObjectLiteralElementLike): string | undefined {
|
|
16
|
+
if (!ts.isPropertyAssignment(prop)) return undefined;
|
|
17
|
+
if (ts.isIdentifier(prop.name) || ts.isStringLiteral(prop.name)) return prop.name.text;
|
|
18
|
+
return undefined;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** The text of a string literal or substitution-free template, else undefined. */
|
|
22
|
+
export function literalText(node: ts.Expression): string | undefined {
|
|
23
|
+
if (ts.isStringLiteral(node) || ts.isNoSubstitutionTemplateLiteral(node)) return node.text;
|
|
24
|
+
return undefined;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function position(source: ts.SourceFile, node: ts.Node): { line: number; column: number } {
|
|
28
|
+
const { line, character } = source.getLineAndCharacterOfPosition(node.getStart(source));
|
|
29
|
+
return { line: line + 1, column: character + 1 };
|
|
30
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import * as ts from "typescript";
|
|
2
|
+
import type { LintRule, LintDiagnostic, LintContext } from "@intentius/chant/lint/rule";
|
|
3
|
+
import { calleeName, literalText, position, propertyName } from "./prom-ast";
|
|
4
|
+
import { checkPromql } from "../../promql";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* PROM002: a rule's `expr`, written as a string literal inside a
|
|
8
|
+
* `RuleGroup`, is not valid PromQL.
|
|
9
|
+
*
|
|
10
|
+
* The same parse runs after the build as PROM104, over every rule in the
|
|
11
|
+
* emitted rule file. This rule reports it in the editor, at the expression,
|
|
12
|
+
* for the literals it can see; an `expr` built at runtime is left to PROM104.
|
|
13
|
+
*/
|
|
14
|
+
export const promqlLiteralRule: LintRule = {
|
|
15
|
+
id: "PROM002",
|
|
16
|
+
severity: "error",
|
|
17
|
+
category: "correctness",
|
|
18
|
+
description: "A literal PromQL expression in a RuleGroup does not parse",
|
|
19
|
+
|
|
20
|
+
check(context: LintContext): LintDiagnostic[] {
|
|
21
|
+
const diagnostics: LintDiagnostic[] = [];
|
|
22
|
+
const source = context.sourceFile;
|
|
23
|
+
|
|
24
|
+
const scan = (node: ts.Node) => {
|
|
25
|
+
if (ts.isPropertyAssignment(node) && propertyName(node) === "expr") {
|
|
26
|
+
const text = literalText(node.initializer);
|
|
27
|
+
if (text !== undefined) {
|
|
28
|
+
const checked = checkPromql(text);
|
|
29
|
+
if (!checked.ok) {
|
|
30
|
+
diagnostics.push({
|
|
31
|
+
ruleId: "PROM002",
|
|
32
|
+
severity: "error",
|
|
33
|
+
message: `expr is not valid PromQL: ${checked.message}`,
|
|
34
|
+
file: context.filePath,
|
|
35
|
+
...position(source, node.initializer),
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
ts.forEachChild(node, scan);
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const visit = (node: ts.Node) => {
|
|
44
|
+
if (ts.isNewExpression(node) && calleeName(node) === "RuleGroup") {
|
|
45
|
+
const arg = node.arguments?.[0];
|
|
46
|
+
if (arg) scan(arg);
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
ts.forEachChild(node, visit);
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
visit(source);
|
|
53
|
+
return diagnostics;
|
|
54
|
+
},
|
|
55
|
+
};
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { describe, expect, test } from "vitest";
|
|
2
|
+
import * as ts from "typescript";
|
|
3
|
+
import type { LintContext } from "@intentius/chant/lint/rule";
|
|
4
|
+
import { literalCredentialRule } from "./literal-credential";
|
|
5
|
+
import { promqlLiteralRule } from "./promql-literal";
|
|
6
|
+
import { sloLiteralRule } from "./slo-literal";
|
|
7
|
+
|
|
8
|
+
function ctx(code: string): LintContext {
|
|
9
|
+
const sourceFile = ts.createSourceFile("alerts.ts", code, ts.ScriptTarget.Latest, true);
|
|
10
|
+
return { sourceFile, entities: [], filePath: "alerts.ts" };
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
describe("PROM001 literal credential", () => {
|
|
14
|
+
test("flags literal secrets in a Receiver, at any depth", () => {
|
|
15
|
+
const diags = literalCredentialRule.check(
|
|
16
|
+
ctx(`
|
|
17
|
+
new Receiver({
|
|
18
|
+
name: "oncall",
|
|
19
|
+
slack_configs: [{ api_url: "https://hooks.slack.com/services/T/B/X", channel: "#a" }],
|
|
20
|
+
pagerduty_configs: [{ routing_key: "abc123" }],
|
|
21
|
+
webhook_configs: [{ url: "http://hook", http_config: { authorization: { credentials: "tok" } } }],
|
|
22
|
+
});
|
|
23
|
+
`),
|
|
24
|
+
);
|
|
25
|
+
expect(diags.map((d) => d.ruleId)).toEqual(["PROM001", "PROM001", "PROM001"]);
|
|
26
|
+
expect(diags[0].message).toContain("api_url_file");
|
|
27
|
+
expect(diags[0].line).toBe(4);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
test("flags the global SMTP password in AlertmanagerSettings", () => {
|
|
31
|
+
const diags = literalCredentialRule.check(ctx(`new AlertmanagerSettings({ global: { smtp_auth_password: "hunter2" } });`));
|
|
32
|
+
expect(diags).toHaveLength(1);
|
|
33
|
+
expect(diags[0].message).toContain("smtp_auth_password_file");
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
test("accepts *_file fields, non-literals, webhook urls and other classes", () => {
|
|
37
|
+
const diags = literalCredentialRule.check(
|
|
38
|
+
ctx(`
|
|
39
|
+
const key = process.env.KEY!;
|
|
40
|
+
new Receiver({ name: "a", pagerduty_configs: [{ routing_key_file: "/etc/pd" }, { routing_key: key }] });
|
|
41
|
+
new Receiver({ name: "b", webhook_configs: [{ url: "http://sink:8080/" }] });
|
|
42
|
+
new Something({ routing_key: "literal" });
|
|
43
|
+
`),
|
|
44
|
+
);
|
|
45
|
+
expect(diags).toEqual([]);
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
describe("PROM002 PromQL literal", () => {
|
|
50
|
+
test("flags a literal expr that does not parse", () => {
|
|
51
|
+
const diags = promqlLiteralRule.check(
|
|
52
|
+
ctx(`
|
|
53
|
+
new RuleGroup({ name: "g", rules: [
|
|
54
|
+
{ record: "a:b", expr: "sum(rate(x[5m])" },
|
|
55
|
+
{ alert: "A", expr: \`up == 0\` },
|
|
56
|
+
] });
|
|
57
|
+
`),
|
|
58
|
+
);
|
|
59
|
+
expect(diags).toHaveLength(1);
|
|
60
|
+
expect(diags[0].ruleId).toBe("PROM002");
|
|
61
|
+
expect(diags[0].line).toBe(3);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
test("ignores built expressions and exprs outside a RuleGroup", () => {
|
|
65
|
+
const diags = promqlLiteralRule.check(
|
|
66
|
+
ctx(`
|
|
67
|
+
const w = "5m";
|
|
68
|
+
new RuleGroup({ name: "g", rules: [{ record: "a:b", expr: \`rate(x[\${w}])\` }] });
|
|
69
|
+
const notARule = { expr: "(((" };
|
|
70
|
+
`),
|
|
71
|
+
);
|
|
72
|
+
expect(diags).toEqual([]);
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
describe("PROM003 Slo literal", () => {
|
|
77
|
+
test("flags an objective outside (0, 1), a bad window and a broken SLI, at the literal", () => {
|
|
78
|
+
const diags = sloLiteralRule.check(
|
|
79
|
+
ctx(`
|
|
80
|
+
export const a = Slo({
|
|
81
|
+
name: "a",
|
|
82
|
+
objective: 99.5,
|
|
83
|
+
window: "4 weeks",
|
|
84
|
+
sli: { good: "sum(rate(ok_total[5m]))", total: "sum(rate(all_total[{{window}}])" },
|
|
85
|
+
});
|
|
86
|
+
export const b = Slo({ name: "b", objective: -1, window: "0", sli: { errors: "sum(rate(e[{{window}}]))", total: "sum(rate(t[{{window}}]))" } });
|
|
87
|
+
`),
|
|
88
|
+
);
|
|
89
|
+
expect(diags.map((d) => [d.ruleId, d.line])).toEqual([
|
|
90
|
+
["PROM003", 4],
|
|
91
|
+
["PROM003", 5],
|
|
92
|
+
["PROM003", 6],
|
|
93
|
+
["PROM003", 6],
|
|
94
|
+
["PROM003", 8],
|
|
95
|
+
["PROM003", 8],
|
|
96
|
+
]);
|
|
97
|
+
expect(diags[0].message).toContain("strictly between 0 and 1");
|
|
98
|
+
expect(diags[1].message).toContain("positive Prometheus duration");
|
|
99
|
+
expect(diags[2].message).toContain("{{window}}");
|
|
100
|
+
expect(diags[3].message).toContain("not valid PromQL");
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
test("accepts a valid Slo, values built at runtime, and other callees", () => {
|
|
104
|
+
const diags = sloLiteralRule.check(
|
|
105
|
+
ctx(`
|
|
106
|
+
const objective = 1.5;
|
|
107
|
+
Slo({ name: "a", objective: 0.999, window: "30d", sli: { good: \`sum(rate(ok[{{window}}]))\`, total: "sum(rate(all[{{window}}]))" } });
|
|
108
|
+
Slo({ name: "b", objective, window: process.env.W!, sli: { good: expr, total: expr } });
|
|
109
|
+
Other({ objective: 5, window: "nope" });
|
|
110
|
+
`),
|
|
111
|
+
);
|
|
112
|
+
expect(diags).toEqual([]);
|
|
113
|
+
});
|
|
114
|
+
});
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import * as ts from "typescript";
|
|
2
|
+
import type { LintRule, LintDiagnostic, LintContext } from "@intentius/chant/lint/rule";
|
|
3
|
+
import { calleeName, literalText, position, propertyName } from "./prom-ast";
|
|
4
|
+
import { sliExprProblem } from "../../composites/slo";
|
|
5
|
+
import { durationMs, isValidDuration } from "../../duration";
|
|
6
|
+
|
|
7
|
+
/** A numeric literal, with an optional leading minus, as a number. */
|
|
8
|
+
function literalNumber(node: ts.Expression): number | undefined {
|
|
9
|
+
if (ts.isNumericLiteral(node)) return Number(node.text);
|
|
10
|
+
if (ts.isPrefixUnaryExpression(node) && node.operator === ts.SyntaxKind.MinusToken && ts.isNumericLiteral(node.operand)) {
|
|
11
|
+
return -Number(node.operand.text);
|
|
12
|
+
}
|
|
13
|
+
return undefined;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function objectProps(node: ts.Expression): Map<string, ts.Expression> {
|
|
17
|
+
const out = new Map<string, ts.Expression>();
|
|
18
|
+
if (!ts.isObjectLiteralExpression(node)) return out;
|
|
19
|
+
for (const p of node.properties) {
|
|
20
|
+
const name = propertyName(p);
|
|
21
|
+
if (name !== undefined && ts.isPropertyAssignment(p)) out.set(name, p.initializer);
|
|
22
|
+
}
|
|
23
|
+
return out;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* PROM003: an `Slo` whose literal objective, window or SLI expression can
|
|
28
|
+
* not build.
|
|
29
|
+
*
|
|
30
|
+
* `Slo()` throws on the same problems when the build runs it; this rule
|
|
31
|
+
* reports them in the editor, at the literal: an objective outside (0, 1),
|
|
32
|
+
* a window that is not a positive Prometheus duration, and an SLI
|
|
33
|
+
* expression without `{{window}}` or that is not PromQL once it is filled
|
|
34
|
+
* in. Values built at runtime are left to the constructor.
|
|
35
|
+
*/
|
|
36
|
+
export const sloLiteralRule: LintRule = {
|
|
37
|
+
id: "PROM003",
|
|
38
|
+
severity: "error",
|
|
39
|
+
category: "correctness",
|
|
40
|
+
description: "An Slo's literal objective, window or SLI expression is invalid",
|
|
41
|
+
|
|
42
|
+
check(context: LintContext): LintDiagnostic[] {
|
|
43
|
+
const diagnostics: LintDiagnostic[] = [];
|
|
44
|
+
const source = context.sourceFile;
|
|
45
|
+
const report = (node: ts.Node, message: string) =>
|
|
46
|
+
diagnostics.push({ ruleId: "PROM003", severity: "error", message, file: context.filePath, ...position(source, node) });
|
|
47
|
+
|
|
48
|
+
const visit = (node: ts.Node) => {
|
|
49
|
+
if ((ts.isCallExpression(node) || ts.isNewExpression(node)) && calleeName(node) === "Slo") {
|
|
50
|
+
const arg = node.arguments?.[0];
|
|
51
|
+
if (arg) {
|
|
52
|
+
const props = objectProps(arg);
|
|
53
|
+
const objective = props.get("objective");
|
|
54
|
+
const o = objective ? literalNumber(objective) : undefined;
|
|
55
|
+
if (objective && o !== undefined && !(o > 0 && o < 1)) {
|
|
56
|
+
report(objective, `Slo objective must be strictly between 0 and 1 (e.g. 0.995 for 99.5%), got ${o}`);
|
|
57
|
+
}
|
|
58
|
+
const window = props.get("window");
|
|
59
|
+
const w = window ? literalText(window) : undefined;
|
|
60
|
+
if (window && w !== undefined && (!isValidDuration(w) || durationMs(w) === 0)) {
|
|
61
|
+
report(window, `Slo window must be a positive Prometheus duration (e.g. 28d or 30d), got ${JSON.stringify(w)}`);
|
|
62
|
+
}
|
|
63
|
+
const sli = props.get("sli");
|
|
64
|
+
if (sli) {
|
|
65
|
+
for (const [key, expr] of objectProps(sli)) {
|
|
66
|
+
if (key !== "good" && key !== "errors" && key !== "total") continue;
|
|
67
|
+
const text = literalText(expr);
|
|
68
|
+
if (text === undefined) continue;
|
|
69
|
+
const problem = sliExprProblem(text);
|
|
70
|
+
if (problem) report(expr, `Slo sli.${key} ${problem}`);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
ts.forEachChild(node, visit);
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
visit(source);
|
|
79
|
+
return diagnostics;
|
|
80
|
+
},
|
|
81
|
+
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { completions } from "./completions";
|
|
3
|
+
|
|
4
|
+
describe("prometheus LSP completions", () => {
|
|
5
|
+
it("offers the entity classes after new", () => {
|
|
6
|
+
const content = 'import { RuleGroup } from "@intentius/chant-lexicon-prometheus";\nconst g = new R';
|
|
7
|
+
const items = completions({
|
|
8
|
+
uri: "file:///rules.ts",
|
|
9
|
+
content,
|
|
10
|
+
position: { line: 1, character: content.split("\n")[1].length },
|
|
11
|
+
wordAtCursor: "R",
|
|
12
|
+
linePrefix: "const g = new R",
|
|
13
|
+
});
|
|
14
|
+
const labels = items.map((i) => i.label);
|
|
15
|
+
expect(labels).toContain("RuleGroup");
|
|
16
|
+
expect(labels).toContain("Route");
|
|
17
|
+
expect(labels).toContain("Receiver");
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it("returns an array for an empty context", () => {
|
|
21
|
+
expect(Array.isArray(completions({} as never))).toBe(true);
|
|
22
|
+
});
|
|
23
|
+
});
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { CompletionContext, CompletionItem } from "@intentius/chant/lsp/types";
|
|
2
|
+
import { LexiconIndex, lexiconCompletions } from "@intentius/chant/lsp/lexicon-providers";
|
|
3
|
+
import { lexiconRegistry } from "../catalog";
|
|
4
|
+
|
|
5
|
+
let cachedIndex: LexiconIndex | null = null;
|
|
6
|
+
|
|
7
|
+
function getIndex(): LexiconIndex {
|
|
8
|
+
cachedIndex ??= new LexiconIndex(lexiconRegistry());
|
|
9
|
+
return cachedIndex;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/** Completions for the prometheus entity classes: RuleGroup and the Alertmanager kinds. */
|
|
13
|
+
export function completions(ctx: CompletionContext): CompletionItem[] {
|
|
14
|
+
return lexiconCompletions(ctx, getIndex(), "Prometheus entity");
|
|
15
|
+
}
|