@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.
Files changed (237) hide show
  1. package/README.md +58 -0
  2. package/dist/alertmanager.d.ts +81 -0
  3. package/dist/alertmanager.d.ts.map +1 -0
  4. package/dist/build.d.ts +33 -0
  5. package/dist/build.d.ts.map +1 -0
  6. package/dist/catalog.d.ts +17 -0
  7. package/dist/catalog.d.ts.map +1 -0
  8. package/dist/codegen/docs-cli.d.ts +3 -0
  9. package/dist/codegen/docs-cli.d.ts.map +1 -0
  10. package/dist/codegen/docs.d.ts +9 -0
  11. package/dist/codegen/docs.d.ts.map +1 -0
  12. package/dist/codegen/generate-cli.d.ts +3 -0
  13. package/dist/codegen/generate-cli.d.ts.map +1 -0
  14. package/dist/codegen/generate.d.ts +19 -0
  15. package/dist/codegen/generate.d.ts.map +1 -0
  16. package/dist/codegen/package.d.ts +7 -0
  17. package/dist/codegen/package.d.ts.map +1 -0
  18. package/dist/composites/catalog.d.ts +3 -0
  19. package/dist/composites/catalog.d.ts.map +1 -0
  20. package/dist/composites/index.d.ts +6 -0
  21. package/dist/composites/index.d.ts.map +1 -0
  22. package/dist/composites/slo.d.ts +205 -0
  23. package/dist/composites/slo.d.ts.map +1 -0
  24. package/dist/detect.d.ts +2 -0
  25. package/dist/detect.d.ts.map +1 -0
  26. package/dist/duration.d.ts +16 -0
  27. package/dist/duration.d.ts.map +1 -0
  28. package/dist/index.d.ts +14 -0
  29. package/dist/index.d.ts.map +1 -0
  30. package/dist/init-templates.d.ts +13 -0
  31. package/dist/init-templates.d.ts.map +1 -0
  32. package/dist/integrity.json +33 -0
  33. package/dist/lint/audit-catalog.d.ts +13 -0
  34. package/dist/lint/audit-catalog.d.ts.map +1 -0
  35. package/dist/lint/post-synth/index.d.ts +3 -0
  36. package/dist/lint/post-synth/index.d.ts.map +1 -0
  37. package/dist/lint/post-synth/prom-helpers.d.ts +33 -0
  38. package/dist/lint/post-synth/prom-helpers.d.ts.map +1 -0
  39. package/dist/lint/post-synth/prom101.d.ts +8 -0
  40. package/dist/lint/post-synth/prom101.d.ts.map +1 -0
  41. package/dist/lint/post-synth/prom102.d.ts +8 -0
  42. package/dist/lint/post-synth/prom102.d.ts.map +1 -0
  43. package/dist/lint/post-synth/prom103.d.ts +8 -0
  44. package/dist/lint/post-synth/prom103.d.ts.map +1 -0
  45. package/dist/lint/post-synth/prom104.d.ts +8 -0
  46. package/dist/lint/post-synth/prom104.d.ts.map +1 -0
  47. package/dist/lint/post-synth/prom105.d.ts +8 -0
  48. package/dist/lint/post-synth/prom105.d.ts.map +1 -0
  49. package/dist/lint/post-synth/prom106.d.ts +8 -0
  50. package/dist/lint/post-synth/prom106.d.ts.map +1 -0
  51. package/dist/lint/post-synth/prom107.d.ts +8 -0
  52. package/dist/lint/post-synth/prom107.d.ts.map +1 -0
  53. package/dist/lint/post-synth/prom201.d.ts +8 -0
  54. package/dist/lint/post-synth/prom201.d.ts.map +1 -0
  55. package/dist/lint/post-synth/prom202.d.ts +8 -0
  56. package/dist/lint/post-synth/prom202.d.ts.map +1 -0
  57. package/dist/lint/post-synth/prom203.d.ts +8 -0
  58. package/dist/lint/post-synth/prom203.d.ts.map +1 -0
  59. package/dist/lint/post-synth/prom204.d.ts +8 -0
  60. package/dist/lint/post-synth/prom204.d.ts.map +1 -0
  61. package/dist/lint/post-synth/prom205.d.ts +8 -0
  62. package/dist/lint/post-synth/prom205.d.ts.map +1 -0
  63. package/dist/lint/post-synth/prom206.d.ts +8 -0
  64. package/dist/lint/post-synth/prom206.d.ts.map +1 -0
  65. package/dist/lint/post-synth/prom207.d.ts +8 -0
  66. package/dist/lint/post-synth/prom207.d.ts.map +1 -0
  67. package/dist/lint/post-synth/prom208.d.ts +8 -0
  68. package/dist/lint/post-synth/prom208.d.ts.map +1 -0
  69. package/dist/lint/post-synth/prom209.d.ts +8 -0
  70. package/dist/lint/post-synth/prom209.d.ts.map +1 -0
  71. package/dist/lint/rules/index.d.ts +7 -0
  72. package/dist/lint/rules/index.d.ts.map +1 -0
  73. package/dist/lint/rules/literal-credential.d.ts +18 -0
  74. package/dist/lint/rules/literal-credential.d.ts.map +1 -0
  75. package/dist/lint/rules/prom-ast.d.ts +14 -0
  76. package/dist/lint/rules/prom-ast.d.ts.map +1 -0
  77. package/dist/lint/rules/promql-literal.d.ts +11 -0
  78. package/dist/lint/rules/promql-literal.d.ts.map +1 -0
  79. package/dist/lint/rules/slo-literal.d.ts +13 -0
  80. package/dist/lint/rules/slo-literal.d.ts.map +1 -0
  81. package/dist/lsp/completions.d.ts +4 -0
  82. package/dist/lsp/completions.d.ts.map +1 -0
  83. package/dist/lsp/hover.d.ts +4 -0
  84. package/dist/lsp/hover.d.ts.map +1 -0
  85. package/dist/manifest.json +8 -0
  86. package/dist/matchers.d.ts +29 -0
  87. package/dist/matchers.d.ts.map +1 -0
  88. package/dist/meta.json +32 -0
  89. package/dist/model.d.ts +262 -0
  90. package/dist/model.d.ts.map +1 -0
  91. package/dist/okf/index.md +34 -0
  92. package/dist/okf/rules/PROM001.md +16 -0
  93. package/dist/okf/rules/PROM002.md +15 -0
  94. package/dist/okf/rules/PROM003.md +11 -0
  95. package/dist/okf/rules/PROM101.md +11 -0
  96. package/dist/okf/rules/PROM102.md +11 -0
  97. package/dist/okf/rules/PROM103.md +11 -0
  98. package/dist/okf/rules/PROM104.md +11 -0
  99. package/dist/okf/rules/PROM105.md +11 -0
  100. package/dist/okf/rules/PROM106.md +11 -0
  101. package/dist/okf/rules/PROM107.md +11 -0
  102. package/dist/okf/rules/PROM201.md +11 -0
  103. package/dist/okf/rules/PROM202.md +11 -0
  104. package/dist/okf/rules/PROM203.md +11 -0
  105. package/dist/okf/rules/PROM204.md +11 -0
  106. package/dist/okf/rules/PROM205.md +11 -0
  107. package/dist/okf/rules/PROM206.md +11 -0
  108. package/dist/okf/rules/PROM207.md +11 -0
  109. package/dist/okf/rules/PROM208.md +11 -0
  110. package/dist/okf/rules/PROM209.md +11 -0
  111. package/dist/okf/types/AlertmanagerSettings.md +13 -0
  112. package/dist/okf/types/InhibitRule.md +9 -0
  113. package/dist/okf/types/Receiver.md +13 -0
  114. package/dist/okf/types/Route.md +9 -0
  115. package/dist/okf/types/RuleGroup.md +13 -0
  116. package/dist/okf/types/TimeInterval.md +9 -0
  117. package/dist/package-cli.d.ts +3 -0
  118. package/dist/package-cli.d.ts.map +1 -0
  119. package/dist/pin.d.ts +20 -0
  120. package/dist/pin.d.ts.map +1 -0
  121. package/dist/plugin.d.ts +10 -0
  122. package/dist/plugin.d.ts.map +1 -0
  123. package/dist/promql.d.ts +25 -0
  124. package/dist/promql.d.ts.map +1 -0
  125. package/dist/rule-eval.d.ts +67 -0
  126. package/dist/rule-eval.d.ts.map +1 -0
  127. package/dist/rules/literal-credential.ts +84 -0
  128. package/dist/rules/prom-ast.ts +30 -0
  129. package/dist/rules/prom-helpers.ts +89 -0
  130. package/dist/rules/prom101.ts +17 -0
  131. package/dist/rules/prom102.ts +17 -0
  132. package/dist/rules/prom103.ts +17 -0
  133. package/dist/rules/prom104.ts +17 -0
  134. package/dist/rules/prom105.ts +17 -0
  135. package/dist/rules/prom106.ts +17 -0
  136. package/dist/rules/prom107.ts +17 -0
  137. package/dist/rules/prom201.ts +17 -0
  138. package/dist/rules/prom202.ts +17 -0
  139. package/dist/rules/prom203.ts +17 -0
  140. package/dist/rules/prom204.ts +17 -0
  141. package/dist/rules/prom205.ts +17 -0
  142. package/dist/rules/prom206.ts +17 -0
  143. package/dist/rules/prom207.ts +17 -0
  144. package/dist/rules/prom208.ts +17 -0
  145. package/dist/rules/prom209.ts +17 -0
  146. package/dist/rules/promql-literal.ts +55 -0
  147. package/dist/rules/slo-literal.ts +81 -0
  148. package/dist/rules.d.ts +59 -0
  149. package/dist/rules.d.ts.map +1 -0
  150. package/dist/serializer.d.ts +27 -0
  151. package/dist/serializer.d.ts.map +1 -0
  152. package/dist/skill-defs.d.ts +3 -0
  153. package/dist/skill-defs.d.ts.map +1 -0
  154. package/dist/skills/chant-prometheus-alertmanager.md +58 -0
  155. package/dist/skills/chant-prometheus-kubernetes.md +48 -0
  156. package/dist/skills/chant-prometheus.md +91 -0
  157. package/dist/tools.d.ts +30 -0
  158. package/dist/tools.d.ts.map +1 -0
  159. package/dist/types/index.d.ts +3 -0
  160. package/dist/validate-cli.d.ts +3 -0
  161. package/dist/validate-cli.d.ts.map +1 -0
  162. package/dist/validate-config.d.ts +35 -0
  163. package/dist/validate-config.d.ts.map +1 -0
  164. package/dist/validate.d.ts +11 -0
  165. package/dist/validate.d.ts.map +1 -0
  166. package/package.json +77 -0
  167. package/src/alertmanager.ts +143 -0
  168. package/src/build.ts +199 -0
  169. package/src/catalog.ts +65 -0
  170. package/src/codegen/docs-cli.ts +4 -0
  171. package/src/codegen/docs.ts +104 -0
  172. package/src/codegen/generate-cli.ts +8 -0
  173. package/src/codegen/generate.ts +49 -0
  174. package/src/codegen/package.ts +41 -0
  175. package/src/composites/catalog.test.ts +37 -0
  176. package/src/composites/catalog.ts +65 -0
  177. package/src/composites/composites.test.ts +256 -0
  178. package/src/composites/index.ts +23 -0
  179. package/src/composites/slo-burn.test.ts +289 -0
  180. package/src/composites/slo.ts +471 -0
  181. package/src/detect.ts +12 -0
  182. package/src/duration.ts +51 -0
  183. package/src/generated/lexicon-prometheus.json +32 -0
  184. package/src/index.ts +97 -0
  185. package/src/init-templates.test.ts +29 -0
  186. package/src/init-templates.ts +76 -0
  187. package/src/lint/audit-catalog.ts +123 -0
  188. package/src/lint/post-synth/index.ts +37 -0
  189. package/src/lint/post-synth/post-synth.test.ts +256 -0
  190. package/src/lint/post-synth/prom-helpers.ts +89 -0
  191. package/src/lint/post-synth/prom101.ts +17 -0
  192. package/src/lint/post-synth/prom102.ts +17 -0
  193. package/src/lint/post-synth/prom103.ts +17 -0
  194. package/src/lint/post-synth/prom104.ts +17 -0
  195. package/src/lint/post-synth/prom105.ts +17 -0
  196. package/src/lint/post-synth/prom106.ts +17 -0
  197. package/src/lint/post-synth/prom107.ts +17 -0
  198. package/src/lint/post-synth/prom201.ts +17 -0
  199. package/src/lint/post-synth/prom202.ts +17 -0
  200. package/src/lint/post-synth/prom203.ts +17 -0
  201. package/src/lint/post-synth/prom204.ts +17 -0
  202. package/src/lint/post-synth/prom205.ts +17 -0
  203. package/src/lint/post-synth/prom206.ts +17 -0
  204. package/src/lint/post-synth/prom207.ts +17 -0
  205. package/src/lint/post-synth/prom208.ts +17 -0
  206. package/src/lint/post-synth/prom209.ts +17 -0
  207. package/src/lint/rules/index.ts +11 -0
  208. package/src/lint/rules/literal-credential.ts +84 -0
  209. package/src/lint/rules/prom-ast.ts +30 -0
  210. package/src/lint/rules/promql-literal.ts +55 -0
  211. package/src/lint/rules/rules.test.ts +114 -0
  212. package/src/lint/rules/slo-literal.ts +81 -0
  213. package/src/lsp/completions.test.ts +23 -0
  214. package/src/lsp/completions.ts +15 -0
  215. package/src/lsp/hover.test.ts +27 -0
  216. package/src/lsp/hover.ts +26 -0
  217. package/src/matchers.ts +132 -0
  218. package/src/model.test.ts +104 -0
  219. package/src/model.ts +290 -0
  220. package/src/package-cli.ts +17 -0
  221. package/src/pin.ts +13 -0
  222. package/src/plugin.test.ts +73 -0
  223. package/src/plugin.ts +129 -0
  224. package/src/promql.ts +46 -0
  225. package/src/rule-eval.ts +397 -0
  226. package/src/rules.ts +127 -0
  227. package/src/serializer.test.ts +179 -0
  228. package/src/serializer.ts +55 -0
  229. package/src/skill-defs.ts +43 -0
  230. package/src/skills/chant-prometheus-alertmanager.md +58 -0
  231. package/src/skills/chant-prometheus-kubernetes.md +48 -0
  232. package/src/skills/chant-prometheus.md +91 -0
  233. package/src/tools.test.ts +119 -0
  234. package/src/tools.ts +70 -0
  235. package/src/validate-cli.ts +7 -0
  236. package/src/validate-config.ts +400 -0
  237. package/src/validate.ts +66 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pin.d.ts","sourceRoot":"","sources":["../src/pin.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,eAAO,MAAM,cAAc;;;;;;;;;EAGzB,CAAC"}
@@ -0,0 +1,10 @@
1
+ import type { LexiconPlugin } from "@intentius/chant/lexicon";
2
+ /**
3
+ * Prometheus lexicon plugin.
4
+ *
5
+ * Typed recording and alerting rule groups, serialized to a Prometheus rule
6
+ * file, and Alertmanager routes, receivers, inhibit rules and time
7
+ * intervals, serialized to `alertmanager.yml`.
8
+ */
9
+ export declare const prometheusPlugin: LexiconPlugin;
10
+ //# sourceMappingURL=plugin.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AA4B9D;;;;;;GAMG;AACH,eAAO,MAAM,gBAAgB,EAAE,aA6F9B,CAAC"}
@@ -0,0 +1,25 @@
1
+ /**
2
+ * PromQL syntax checking, with the grammar the Prometheus project publishes
3
+ * (`@prometheus-io/lezer-promql`, the parser behind the Prometheus web UI's
4
+ * editor).
5
+ *
6
+ * This is a syntax check: unbalanced brackets, a bad duration in a range
7
+ * selector, an unknown function, two selectors with no operator between
8
+ * them. It does not type-check (`rate` over an instant vector parses), which
9
+ * `promtool check rules` does when it is available (see `tools.ts`).
10
+ */
11
+ /** The grammar package and the exact version this lexicon checks PromQL with. */
12
+ export declare const PROMQL_GRAMMAR: Readonly<{
13
+ source: "@prometheus-io/lezer-promql";
14
+ version: "0.315.0";
15
+ }>;
16
+ export type PromqlCheck = {
17
+ ok: true;
18
+ } | {
19
+ ok: false;
20
+ position: number;
21
+ message: string;
22
+ };
23
+ /** Check one PromQL expression's syntax. */
24
+ export declare function checkPromql(expr: string): PromqlCheck;
25
+ //# sourceMappingURL=promql.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"promql.d.ts","sourceRoot":"","sources":["../src/promql.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAIH,iFAAiF;AACjF,eAAO,MAAM,cAAc;;;EAA+E,CAAC;AAE3G,MAAM,MAAM,WAAW,GAAG;IAAE,EAAE,EAAE,IAAI,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC;AAU1F,4CAA4C;AAC5C,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,WAAW,CAkBrD"}
@@ -0,0 +1,67 @@
1
+ /**
2
+ * A small rule-file evaluator, for tests over synthetic series.
3
+ *
4
+ * It evaluates recording and alerting rules the way Prometheus does, one
5
+ * evaluation step at a time: each group's rules in order, recording rule
6
+ * results written back as series the later rules (and later steps) read.
7
+ * The PromQL it understands is the subset the lexicon's generated rules use:
8
+ * number literals, vector and range selectors with `=`, `!=`, `=~` and `!~`
9
+ * matchers, `rate`, `increase`, `avg_over_time`, `sum_over_time`,
10
+ * `count_over_time`, `vector`, `sum` (with `by` or `without`), the
11
+ * arithmetic and comparison operators and `and`, `or` and `unless` (with
12
+ * `on` or `ignoring`). Anything else throws, so a test never passes by
13
+ * silently evaluating something it does not understand.
14
+ *
15
+ * `rate` is the increase between the first and last sample in the range
16
+ * over the time between them, without Prometheus's extrapolation to the
17
+ * range's edges. For two counters sampled together that changes neither's
18
+ * ratio, which is what an SLI is. `promtool test rules`, when installed,
19
+ * checks the same rules with Prometheus's own engine.
20
+ *
21
+ * Not exported from the package root; tests import it by path.
22
+ */
23
+ import { type RuleGroupConfig } from "./model.js";
24
+ export type Labels = Record<string, string>;
25
+ export interface Sample {
26
+ t: number;
27
+ v: number;
28
+ }
29
+ export interface Series {
30
+ labels: Labels;
31
+ samples: Sample[];
32
+ }
33
+ export interface VectorElement {
34
+ labels: Labels;
35
+ value: number;
36
+ }
37
+ /** One firing alert at one evaluation. */
38
+ export interface FiringAlert {
39
+ labels: Labels;
40
+ value: number;
41
+ }
42
+ /** An in-memory series store with rule evaluation over it. */
43
+ export declare class RuleEvaluator {
44
+ private readonly groups;
45
+ private readonly series;
46
+ /** When each active alert started, per alerting rule. */
47
+ private readonly pending;
48
+ constructor(groups: RuleGroupConfig[]);
49
+ /** Append one sample; samples of a series must arrive in time order. */
50
+ add(labels: Labels, t: number, v: number): void;
51
+ /** Evaluate every group at time `t` (milliseconds); returns the alerts firing at `t`. */
52
+ step(t: number): FiringAlert[];
53
+ /** The latest value of each series matching `selector` at `t`, e.g. `slo:x{slo="a"}`. */
54
+ query(expr: string, t: number): VectorElement[];
55
+ private eval;
56
+ private text;
57
+ private matchers;
58
+ private select;
59
+ /** Samples in (t - range, t]. */
60
+ private window;
61
+ private node;
62
+ private args;
63
+ private call;
64
+ private aggregate;
65
+ private binary;
66
+ }
67
+ //# sourceMappingURL=rule-eval.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rule-eval.d.ts","sourceRoot":"","sources":["../src/rule-eval.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAIH,OAAO,EAA8D,KAAK,eAAe,EAAE,MAAM,SAAS,CAAC;AAI3G,MAAM,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;AAC5C,MAAM,WAAW,MAAM;IACrB,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;CACX;AACD,MAAM,WAAW,MAAM;IACrB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB;AACD,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;CACf;AAGD,0CAA0C;AAC1C,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;CACf;AAiCD,8DAA8D;AAC9D,qBAAa,aAAa;IAKZ,OAAO,CAAC,QAAQ,CAAC,MAAM;IAJnC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAA6B;IACpD,yDAAyD;IACzD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA0C;gBAErC,MAAM,EAAE,eAAe,EAAE;IAEtD,wEAAwE;IACxE,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,IAAI;IAU/C,yFAAyF;IACzF,IAAI,CAAC,CAAC,EAAE,MAAM,GAAG,WAAW,EAAE;IA8B9B,yFAAyF;IACzF,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,aAAa,EAAE;IAK/C,OAAO,CAAC,IAAI;IAOZ,OAAO,CAAC,IAAI;IAIZ,OAAO,CAAC,QAAQ;IAiBhB,OAAO,CAAC,MAAM;IAId,iCAAiC;IACjC,OAAO,CAAC,MAAM;IAcd,OAAO,CAAC,IAAI;IAyBZ,OAAO,CAAC,IAAI;IAQZ,OAAO,CAAC,IAAI;IAuCZ,OAAO,CAAC,SAAS;IA4BjB,OAAO,CAAC,MAAM;CA0Gf"}
@@ -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,89 @@
1
+ /**
2
+ * Shared plumbing for the prometheus post-synth checks: find the rule files
3
+ * and `alertmanager.yml` documents in a build's output, and run the
4
+ * plain-function checks over them.
5
+ *
6
+ * Documents are recognized by shape, not by which lexicon emitted them, so a
7
+ * rule file that reaches the output some other way (a hand-written YAML
8
+ * sidecar) is checked too. `PrometheusRule` manifests are not read here;
9
+ * their groups are the same `RuleGroup`s and are checked where they are
10
+ * declared.
11
+ */
12
+
13
+ import type { PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
14
+ import type { SerializerResult } from "@intentius/chant/serializer";
15
+ import { loadAll } from "js-yaml";
16
+ import { looksLikeAlertmanagerConfig, looksLikeRuleFile, type AlertmanagerConfig, type RuleFileConfig } from "../../model";
17
+ import {
18
+ validateAlertmanagerConfig,
19
+ validateRuleFile,
20
+ validateSeverityRouting,
21
+ type PrometheusIssue,
22
+ type PrometheusIssueCode,
23
+ } from "../../validate-config";
24
+
25
+ export interface FoundDocs {
26
+ ruleFiles: Array<{ source: string; config: RuleFileConfig }>;
27
+ alertmanager: Array<{ source: string; config: AlertmanagerConfig }>;
28
+ }
29
+
30
+ /** Every rule file and Alertmanager config in the output. Parsed with js-yaml rather than `ctx.docs`, which keeps block scalars and flow lists as strings. */
31
+ export function prometheusDocs(ctx: PostSynthContext): FoundDocs {
32
+ const found: FoundDocs = { ruleFiles: [], alertmanager: [] };
33
+ for (const [lexicon, output] of ctx.outputs) {
34
+ const texts: Array<[string, string]> =
35
+ typeof output === "string"
36
+ ? [[lexicon, output]]
37
+ : [[lexicon, (output as SerializerResult).primary], ...Object.entries((output as SerializerResult).files ?? {})];
38
+ for (const [source, text] of texts) {
39
+ if (!text) continue;
40
+ let docs: unknown[];
41
+ try {
42
+ docs = loadAll(text);
43
+ } catch {
44
+ continue; // not YAML, or not ours
45
+ }
46
+ for (const doc of docs) {
47
+ if (looksLikeRuleFile(doc)) found.ruleFiles.push({ source, config: doc });
48
+ else if (looksLikeAlertmanagerConfig(doc)) found.alertmanager.push({ source, config: doc });
49
+ }
50
+ }
51
+ }
52
+ return found;
53
+ }
54
+
55
+ function toDiagnostic(issue: PrometheusIssue, source: string): PostSynthDiagnostic {
56
+ return {
57
+ checkId: issue.code,
58
+ severity: issue.severity,
59
+ message: source && source !== "prometheus" ? `${source}: ${issue.message}` : issue.message,
60
+ ...(issue.subject ? { entity: issue.subject } : {}),
61
+ lexicon: "prometheus",
62
+ };
63
+ }
64
+
65
+ /** Diagnostics for one rule-file code (PROM101-PROM107) across every rule file in the output. */
66
+ export function ruleFileDiagnostics(ctx: PostSynthContext, code: PrometheusIssueCode): PostSynthDiagnostic[] {
67
+ return prometheusDocs(ctx).ruleFiles.flatMap(({ source, config }) =>
68
+ validateRuleFile(config)
69
+ .filter((i) => i.code === code)
70
+ .map((i) => toDiagnostic(i, source)),
71
+ );
72
+ }
73
+
74
+ /** Diagnostics for one Alertmanager code (PROM201, PROM203-PROM209) across every `alertmanager.yml` in the output. */
75
+ export function alertmanagerDiagnostics(ctx: PostSynthContext, code: PrometheusIssueCode): PostSynthDiagnostic[] {
76
+ return prometheusDocs(ctx).alertmanager.flatMap(({ source, config }) =>
77
+ validateAlertmanagerConfig(config)
78
+ .filter((i) => i.code === code)
79
+ .map((i) => toDiagnostic(i, source)),
80
+ );
81
+ }
82
+
83
+ /** PROM202: joins every rule file in the output against every Alertmanager config in it. Silent when either is absent. */
84
+ export function routingDiagnostics(ctx: PostSynthContext): PostSynthDiagnostic[] {
85
+ const { ruleFiles, alertmanager } = prometheusDocs(ctx);
86
+ if (ruleFiles.length === 0) return [];
87
+ const files = ruleFiles.map((r) => r.config);
88
+ return alertmanager.flatMap(({ source, config }) => validateSeverityRouting(files, config).map((i) => toDiagnostic(i, source)));
89
+ }
@@ -0,0 +1,17 @@
1
+ /**
2
+ * PROM101: Two rule groups in one rule file share a name
3
+ *
4
+ * Prometheus refuses to load a rule file whose group names repeat.
5
+ */
6
+
7
+ import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
8
+ import { ruleFileDiagnostics } from "./prom-helpers";
9
+
10
+ export const prom101: PostSynthCheck = {
11
+ id: "PROM101",
12
+ description: "Two rule groups in one rule file share a name",
13
+
14
+ check(ctx: PostSynthContext): PostSynthDiagnostic[] {
15
+ return ruleFileDiagnostics(ctx, "PROM101");
16
+ },
17
+ };
@@ -0,0 +1,17 @@
1
+ /**
2
+ * PROM102: Two rules share a name and label set
3
+ *
4
+ * Two recording rules (or two alerts) with the same name and labels produce the same series and overwrite each other. This follows promtool's duplicate-rules lint: alerts that share a name and differ by a label, such as severity, are fine.
5
+ */
6
+
7
+ import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
8
+ import { ruleFileDiagnostics } from "./prom-helpers";
9
+
10
+ export const prom102: PostSynthCheck = {
11
+ id: "PROM102",
12
+ description: "Two rules share a name and label set",
13
+
14
+ check(ctx: PostSynthContext): PostSynthDiagnostic[] {
15
+ return ruleFileDiagnostics(ctx, "PROM102");
16
+ },
17
+ };
@@ -0,0 +1,17 @@
1
+ /**
2
+ * PROM103: A rule or group duration is not a Prometheus duration
3
+ *
4
+ * for, keep_firing_for, interval and query_offset take durations such as 30s, 5m or 1h30m. Prometheus refuses the rule file otherwise.
5
+ */
6
+
7
+ import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
8
+ import { ruleFileDiagnostics } from "./prom-helpers";
9
+
10
+ export const prom103: PostSynthCheck = {
11
+ id: "PROM103",
12
+ description: "A rule or group duration is not a Prometheus duration",
13
+
14
+ check(ctx: PostSynthContext): PostSynthDiagnostic[] {
15
+ return ruleFileDiagnostics(ctx, "PROM103");
16
+ },
17
+ };
@@ -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
+ };