@intentius/chant-lexicon-grafana 0.106.1 → 0.106.4

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.
@@ -3,7 +3,9 @@
3
3
  * `chant import` (#3538).
4
4
  *
5
5
  * `GrafanaOperatorResources` (#3156) writes `GrafanaAlertRuleGroup`,
6
- * `GrafanaContactPoint`, `GrafanaNotificationPolicy`, `GrafanaMuteTiming` and
6
+ * `GrafanaContactPoint`, `GrafanaNotificationPolicy` (with, for
7
+ * `policyRoutes`, a `GrafanaNotificationPolicyRoute` per child route),
8
+ * `GrafanaMuteTiming` and
7
9
  * `GrafanaNotificationTemplate` from grafana alerting declarations. The k8s
8
10
  * parser offers the one field of each that holds the alerting content
9
11
  * (`spec.rules`, `spec.receivers`, `spec.route`, `spec.time_intervals`,
@@ -26,6 +28,9 @@
26
28
  * name is not part of a declaration, so a contact point that reads a
27
29
  * Secret is written with a small module holding
28
30
  * `operatorReceivers(contactPoint, "<secret>")`;
31
+ * - a `GrafanaNotificationPolicyRoute`: its whole `spec` is the route, read
32
+ * as the one child of a policy that has its receiver, and written back by
33
+ * `operatorRouteSpec`;
29
34
  * - the policy tree: `object_matchers` go back to Alertmanager `matchers`
30
35
  * strings where each can be written as one.
31
36
  *
@@ -1 +1 @@
1
- {"version":3,"file":"operator.d.ts","sourceRoot":"","sources":["../../src/import/operator.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AAEH,OAAO,KAAK,EAAmB,uBAAuB,EAAkB,MAAM,kCAAkC,CAAC;AAKjH,KAAK,IAAI,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAkBpC,0FAA0F;AAC1F,wBAAgB,eAAe,CAAC,IAAI,EAAE,IAAI,GAAG,IAAI,CAwBhD;AAYD;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,SAAS,EAAE,OAAO,EAAE,EAAE,KAAK,EAAE,MAAM,GAAG;IAAE,SAAS,EAAE,IAAI,EAAE,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,CAAA;CAAE,CAoBpH;AAWD,oIAAoI;AACpI,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,IAAI,GAAG,IAAI,CAWlD;AAwCD,eAAO,MAAM,gBAAgB,EAAE,uBA2C9B,CAAC"}
1
+ {"version":3,"file":"operator.d.ts","sourceRoot":"","sources":["../../src/import/operator.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AAEH,OAAO,KAAK,EAAmB,uBAAuB,EAAkB,MAAM,kCAAkC,CAAC;AAKjH,KAAK,IAAI,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAmBpC,0FAA0F;AAC1F,wBAAgB,eAAe,CAAC,IAAI,EAAE,IAAI,GAAG,IAAI,CAwBhD;AAYD;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,SAAS,EAAE,OAAO,EAAE,EAAE,KAAK,EAAE,MAAM,GAAG;IAAE,SAAS,EAAE,IAAI,EAAE,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,CAAA;CAAE,CAoBpH;AAWD,oIAAoI;AACpI,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,IAAI,GAAG,IAAI,CAWlD;AAiDD,eAAO,MAAM,gBAAgB,EAAE,uBA2C9B,CAAC"}
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "algorithm": "sha256",
3
3
  "artifacts": {
4
- "manifest.json": "06345c465338218becce11ad5a3cf5e8c130d367b0eb9f8044886d2a5987489a",
4
+ "manifest.json": "96e7d55ee2dd5c15bbaee4a90ce946dad5a429d9afbc45268b4ac11e746f05a3",
5
5
  "meta.json": "330a1598c3b35ae62267e886a3416a494b0ebe9f1d2e2379253787d0a0bcb32e",
6
6
  "types/index.d.ts": "ad385264290f2486f7a54be205684de8415a25b74eee5b809ca625ae19fb08da",
7
7
  "rules/grafana-ast.ts": "51f4fac423f7a5c6d6f577074e95b4a70d4ab0a05883a86e04970a50c63d92e8",
@@ -31,5 +31,5 @@
31
31
  "skills/chant-grafana-alerting.md": "d9648dc14c848851bdccbe069dac54ad06115066f9466c90c4137d07dde6d4ad",
32
32
  "skills/chant-grafana-operations.md": "4e665f80292ab32bde6d77940cc3f3923678c58e6db0b80613489050d2cb665b"
33
33
  },
34
- "composite": "4a36d87b528d23995dab52878e7e42c4429aa9611a9b9bef2c133e9c4e2c7ba5"
34
+ "composite": "6d41cceae3d62449dcb425c4231aaac8dae3adbd8c51fbcee8d1ca1da701e239"
35
35
  }
package/dist/k8s.d.ts CHANGED
@@ -35,7 +35,7 @@
35
35
  */
36
36
  import { type CompositeInstance } from "@intentius/chant/composite";
37
37
  import type { Declarable } from "@intentius/chant/declarable";
38
- import { ConfigMap, GrafanaAlertRuleGroup, GrafanaContactPoint, GrafanaDashboard, GrafanaDatasource, GrafanaFolder, GrafanaLibraryPanel, GrafanaMuteTiming, GrafanaNotificationPolicy, GrafanaNotificationTemplate } from "@intentius/chant-lexicon-k8s/generated/index";
38
+ import { ConfigMap, GrafanaAlertRuleGroup, GrafanaContactPoint, GrafanaDashboard, GrafanaDatasource, GrafanaFolder, GrafanaLibraryPanel, GrafanaMuteTiming, GrafanaNotificationPolicy, GrafanaNotificationPolicyRoute, GrafanaNotificationTemplate } from "@intentius/chant-lexicon-k8s/generated/index";
39
39
  import { type ProvisionedAlertRule, type ProvisionedContactPoint } from "./alerting-build.js";
40
40
  import type { AlertRuleGroupEntity, ContactPointEntity, MuteTimingEntity, NotificationPolicyEntity, NotificationTemplateEntity } from "./alerting.js";
41
41
  import { type ProvisionedDatasource } from "./build.js";
@@ -163,8 +163,18 @@ export interface GrafanaOperatorResourcesProps {
163
163
  * contact point has such a reference.
164
164
  */
165
165
  secretName?: string;
166
+ /**
167
+ * Write each direct child route of a notification policy as a
168
+ * `GrafanaNotificationPolicyRoute` of its own, which the policy merges in
169
+ * through `spec.route.routeSelector` (the operator allows `routes` or
170
+ * `routeSelector`, not both). The routes carry the label
171
+ * `grafana.chant.dev/policy: <policy resource name>`, which the selector
172
+ * matches. A route's own nested routes stay inline in its `spec.routes`.
173
+ * Defaults to false: the whole tree inline in `spec.route`.
174
+ */
175
+ policyRoutes?: boolean;
166
176
  }
167
- type OperatorEntity = (InstanceType<typeof GrafanaDashboard> | InstanceType<typeof GrafanaDatasource> | InstanceType<typeof GrafanaFolder> | InstanceType<typeof GrafanaLibraryPanel> | InstanceType<typeof GrafanaAlertRuleGroup> | InstanceType<typeof GrafanaContactPoint> | InstanceType<typeof GrafanaNotificationPolicy> | InstanceType<typeof GrafanaMuteTiming> | InstanceType<typeof GrafanaNotificationTemplate>) & Declarable;
177
+ type OperatorEntity = (InstanceType<typeof GrafanaDashboard> | InstanceType<typeof GrafanaDatasource> | InstanceType<typeof GrafanaFolder> | InstanceType<typeof GrafanaLibraryPanel> | InstanceType<typeof GrafanaAlertRuleGroup> | InstanceType<typeof GrafanaContactPoint> | InstanceType<typeof GrafanaNotificationPolicy> | InstanceType<typeof GrafanaNotificationPolicyRoute> | InstanceType<typeof GrafanaMuteTiming> | InstanceType<typeof GrafanaNotificationTemplate>) & Declarable;
168
178
  export type GrafanaOperatorResourcesMembers = Record<string, OperatorEntity>;
169
179
  export type GrafanaOperatorResourcesInstance = CompositeInstance<GrafanaOperatorResourcesMembers> & GrafanaOperatorResourcesMembers;
170
180
  /**
@@ -178,6 +188,8 @@ export declare function operatorDatasource(ds: ProvisionedDatasource, secretName
178
188
  datasource: Record<string, unknown>;
179
189
  valuesFrom: Array<Record<string, unknown>>;
180
190
  };
191
+ /** The label a `GrafanaNotificationPolicyRoute` carries for the policy whose `routeSelector` merges it in. */
192
+ export declare const POLICY_ROUTE_LABEL = "grafana.chant.dev/policy";
181
193
  /**
182
194
  * One contact point receiver's settings for `spec.receivers[].settings`, and
183
195
  * the `valuesFrom` entries for the secrets in it. A setting that is wholly
@@ -216,6 +228,12 @@ export declare function operatorRules(group: AlertRuleGroupEntity): Array<Record
216
228
  export declare function operatorReceivers(cp: ContactPointEntity, secretName?: string): Array<Record<string, unknown>>;
217
229
  /** `GrafanaNotificationPolicy.spec.route` for a policy tree. */
218
230
  export declare function operatorPolicy(policy: NotificationPolicyEntity): Record<string, unknown>;
231
+ /**
232
+ * `GrafanaNotificationPolicyRoute.spec` for a policy declaration holding the
233
+ * route as its one child: the child's fields, with `receiver` always set
234
+ * (the CRD requires it, so a child that inherits it gets the policy's).
235
+ */
236
+ export declare function operatorRouteSpec(policy: NotificationPolicyEntity): Record<string, unknown>;
219
237
  /** `GrafanaMuteTiming.spec.time_intervals` for a mute timing. */
220
238
  export declare function operatorTimeIntervals(timing: MuteTimingEntity): Array<Record<string, unknown>>;
221
239
  /** `GrafanaNotificationTemplate.spec.template` for a template. */
@@ -241,7 +259,9 @@ export declare function operatorTemplate(template: NotificationTemplateEntity):
241
259
  * a `GrafanaContactPoint` per contact point, `<name>-contact-point-<name>`,
242
260
  * whose `${NAME}` secrets go to each receiver's `valuesFrom`; a
243
261
  * `GrafanaNotificationPolicy`, `<name>-notification-policy`, with its
244
- * nested routes inline; a `GrafanaMuteTiming` per mute timing and a
262
+ * nested routes inline (or, with `policyRoutes`, one
263
+ * `GrafanaNotificationPolicyRoute` per direct child route, selected by
264
+ * label); a `GrafanaMuteTiming` per mute timing and a
245
265
  * `GrafanaNotificationTemplate` per template.
246
266
  *
247
267
  * Every resource carries `instanceSelector`. Export the result from a k8s
package/dist/k8s.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"k8s.d.ts","sourceRoot":"","sources":["../src/k8s.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"k8s.d.ts","sourceRoot":"","sources":["../src/k8s.ts"],"names":[],"mappings":"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"}
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "grafana",
3
- "version": "0.106.1",
3
+ "version": "0.106.4",
4
4
  "chantVersion": ">=0.1.0",
5
5
  "namespace": "Grafana",
6
6
  "intrinsics": [],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@intentius/chant-lexicon-grafana",
3
- "version": "0.106.1",
3
+ "version": "0.106.4",
4
4
  "description": "Grafana lexicon for chant — typed dashboards, panels, queries and datasources, serialized to dashboard JSON and Grafana provisioning files",
5
5
  "license": "Apache-2.0",
6
6
  "homepage": "https://intentius.io/chant",
@@ -83,7 +83,7 @@
83
83
  "zod": "^4.3.6"
84
84
  },
85
85
  "peerDependencies": {
86
- "@intentius/chant": "^0.106.1",
86
+ "@intentius/chant": "^0.106.4",
87
87
  "typescript": "^5.9.3",
88
88
  "zod": "^4.3.6"
89
89
  }
@@ -1,7 +1,7 @@
1
1
  import { describe, expect, test } from "vitest";
2
2
  import type { EmbeddedContent } from "@intentius/chant/import/embedded";
3
3
  import { grafanaPlugin } from "../plugin";
4
- import { operatorContactPoint, operatorPolicyRoute, operatorRule } from "../k8s";
4
+ import { operatorContactPoint, operatorPolicyRoute, operatorRouteSpec, operatorRule } from "../k8s";
5
5
  import { operatorImporter, provisionedReceivers, provisionedRoute, provisionedRule } from "./operator";
6
6
 
7
7
  const site = (hostType: string, select: string, spec: Record<string, unknown>): EmbeddedContent => ({
@@ -25,6 +25,7 @@ describe("Grafana Operator alerting embedded in a k8s manifest (#3538)", () => {
25
25
  expect(operatorImporter.matches(site("K8s::Grafana::GrafanaAlertRuleGroup", "rules", { rules: [] }))).toBe(true);
26
26
  expect(operatorImporter.matches(site("K8s::Grafana::GrafanaContactPoint", "receivers", { receivers: [] }))).toBe(true);
27
27
  expect(operatorImporter.matches(site("K8s::Grafana::GrafanaNotificationPolicy", "route", { route: {} }))).toBe(true);
28
+ expect(operatorImporter.matches(site("K8s::Grafana::GrafanaNotificationPolicyRoute", "route", { route: { receiver: "r" } }))).toBe(true);
28
29
  expect(operatorImporter.matches(site("K8s::Grafana::GrafanaMuteTiming", "time_intervals", { time_intervals: [] }))).toBe(true);
29
30
  expect(operatorImporter.matches(site("K8s::Grafana::GrafanaNotificationTemplate", "template", { template: "x" }))).toBe(true);
30
31
  expect(operatorImporter.matches(site("K8s::Grafana::GrafanaAlertRuleGroup", "receivers", {}))).toBe(false);
@@ -205,6 +206,27 @@ describe("Grafana Operator alerting embedded in a k8s manifest (#3538)", () => {
205
206
  expect(template.files.map((f) => f.content).join("\n")).toContain("new NotificationTemplate(");
206
207
  });
207
208
 
209
+ test("a policy route is read as the child of a policy and written back through operatorRouteSpec", () => {
210
+ const spec = { receiver: "tickets", object_matchers: [["severity", "=", "ticket"]], mute_time_intervals: ["weekends"], routes: [{ object_matchers: [["team", "=", "a"]] }] };
211
+ const out = operatorImporter.import(site("K8s::Grafana::GrafanaNotificationPolicyRoute", "route", { route: spec }));
212
+ expect(out.value.through).toEqual({ from: "@intentius/chant-lexicon-grafana/k8s", name: "operatorRouteSpec" });
213
+ const text = out.files.map((f) => f.content).join("\n");
214
+ expect(text).toContain("new NotificationPolicy(");
215
+ expect(text).toContain('severity="ticket"');
216
+ expect(text).toContain('team="a"');
217
+ });
218
+
219
+ test("a policy route's routeSelector is left out with a warning, and a route with no receiver throws", () => {
220
+ const out = operatorImporter.import(site("K8s::Grafana::GrafanaNotificationPolicyRoute", "route", { route: { receiver: "r", routeSelector: { matchLabels: { a: "b" } } } }));
221
+ expect((out.warnings ?? []).join("\n")).toContain("routeSelector");
222
+ expect(() => operatorImporter.import(site("K8s::Grafana::GrafanaNotificationPolicyRoute", "route", { route: { matchers: [] } }))).toThrow(/spec.receiver/);
223
+ });
224
+
225
+ test("a policy that selects routes by label throws so the host keeps it as written", () => {
226
+ const route = { receiver: "oncall", routeSelector: { matchLabels: { a: "b" } } };
227
+ expect(() => operatorImporter.import(site("K8s::Grafana::GrafanaNotificationPolicy", "route", { route }))).toThrow(/routeSelector/);
228
+ });
229
+
208
230
  test("a policy with no receiver, and a group with no folder, throw so the host keeps them as written", () => {
209
231
  expect(() => operatorImporter.import(site("K8s::Grafana::GrafanaNotificationPolicy", "route", { route: { group_by: ["a"] } }))).toThrow();
210
232
  expect(() => operatorImporter.import(site("K8s::Grafana::GrafanaAlertRuleGroup", "rules", { name: "g", rules: [] }))).toThrow(/names no folder/);
@@ -3,7 +3,9 @@
3
3
  * `chant import` (#3538).
4
4
  *
5
5
  * `GrafanaOperatorResources` (#3156) writes `GrafanaAlertRuleGroup`,
6
- * `GrafanaContactPoint`, `GrafanaNotificationPolicy`, `GrafanaMuteTiming` and
6
+ * `GrafanaContactPoint`, `GrafanaNotificationPolicy` (with, for
7
+ * `policyRoutes`, a `GrafanaNotificationPolicyRoute` per child route),
8
+ * `GrafanaMuteTiming` and
7
9
  * `GrafanaNotificationTemplate` from grafana alerting declarations. The k8s
8
10
  * parser offers the one field of each that holds the alerting content
9
11
  * (`spec.rules`, `spec.receivers`, `spec.route`, `spec.time_intervals`,
@@ -26,6 +28,9 @@
26
28
  * name is not part of a declaration, so a contact point that reads a
27
29
  * Secret is written with a small module holding
28
30
  * `operatorReceivers(contactPoint, "<secret>")`;
31
+ * - a `GrafanaNotificationPolicyRoute`: its whole `spec` is the route, read
32
+ * as the one child of a policy that has its receiver, and written back by
33
+ * `operatorRouteSpec`;
29
34
  * - the policy tree: `object_matchers` go back to Alertmanager `matchers`
30
35
  * strings where each can be written as one.
31
36
  *
@@ -48,6 +53,7 @@ const KINDS: Record<string, { select: string; className: string; through: string
48
53
  "K8s::Grafana::GrafanaAlertRuleGroup": { select: "rules", className: "AlertRuleGroup", through: "operatorRules" },
49
54
  "K8s::Grafana::GrafanaContactPoint": { select: "receivers", className: "ContactPoint", through: "operatorReceivers" },
50
55
  "K8s::Grafana::GrafanaNotificationPolicy": { select: "route", className: "NotificationPolicy", through: "operatorPolicy" },
56
+ "K8s::Grafana::GrafanaNotificationPolicyRoute": { select: "route", className: "NotificationPolicy", through: "operatorRouteSpec" },
51
57
  "K8s::Grafana::GrafanaMuteTiming": { select: "time_intervals", className: "MuteTiming", through: "operatorTimeIntervals" },
52
58
  "K8s::Grafana::GrafanaNotificationTemplate": { select: "template", className: "NotificationTemplate", through: "operatorTemplate" },
53
59
  };
@@ -169,8 +175,17 @@ function provisioningFile(type: string, spec: Json, warnings: string[]): { file:
169
175
  }
170
176
  case "K8s::Grafana::GrafanaNotificationPolicy": {
171
177
  if (!isObject(spec.route)) throw new Error(`${where} has no spec.route`);
178
+ // The routes it selects are their own resources, and a declaration cannot hold the selector: keep it as written.
179
+ if (spec.route.routeSelector !== undefined) throw new Error(`${where} merges routes in with spec.route.routeSelector, which a NotificationPolicy cannot hold`);
172
180
  return { file: { apiVersion: 1, policies: [provisionedRoute(spec.route)] } };
173
181
  }
182
+ case "K8s::Grafana::GrafanaNotificationPolicyRoute": {
183
+ // The k8s parser offers the whole spec as `route`. A route has no declaration of its own, so it is read as the one child of a policy with its receiver.
184
+ if (!isObject(spec.route) || typeof spec.route.receiver !== "string") throw new Error(`${where} has no spec.receiver`);
185
+ const { routeSelector: _selector, ...route } = spec.route;
186
+ if (_selector !== undefined) warnings.push("the route's routeSelector (routes merged in by label) is not part of a declaration, so it is left out; import the selected routes on their own");
187
+ return { file: { apiVersion: 1, policies: [{ receiver: route.receiver, routes: [provisionedRoute(route)] }] } };
188
+ }
174
189
  case "K8s::Grafana::GrafanaMuteTiming": {
175
190
  if (typeof name !== "string") throw new Error(`${where} has no spec.name`);
176
191
  return { file: { apiVersion: 1, muteTimes: [{ name, time_intervals: spec.time_intervals }] } };
package/src/k8s.test.ts CHANGED
@@ -18,6 +18,7 @@ import {
18
18
  operatorDatasource,
19
19
  operatorPolicyRoute,
20
20
  operatorReceiverSettings,
21
+ operatorRouteSpec,
21
22
  operatorRule,
22
23
  } from "./k8s";
23
24
 
@@ -327,6 +328,12 @@ describe("GrafanaOperatorResources alerting (#3156)", () => {
327
328
  ]);
328
329
  expect(() => operatorReceiverSettings("cp", { url: "https://x/${TOKEN}" }, "s")).toThrow(/inside the text/);
329
330
  expect(() => operatorReceiverSettings("cp", { password: "$__file{/run/pw}" }, "s")).toThrow(/inside the text/);
331
+ // The alert template variables are not references, even when they are the whole setting.
332
+ expect(operatorReceiverSettings("cp", { message: "$labels", detail: "$value", more: "$values" }, undefined)).toEqual({
333
+ settings: { message: "$labels", detail: "$value", more: "$values" },
334
+ valuesFrom: [],
335
+ });
336
+ expect(operatorReceiverSettings("cp", { url: "$labelsToken" }, "s").valuesFrom).toEqual([{ targetPath: "url", valueFrom: { secretKeyRef: { name: "s", key: "labelsToken" } } }]);
330
337
  // Go template text with a plain `$` is not a reference.
331
338
  expect(operatorReceiverSettings("cp", { message: "{{ $labels.job }} is down" }, undefined)).toEqual({ settings: { message: "{{ $labels.job }} is down" }, valuesFrom: [] });
332
339
  });
@@ -349,6 +356,33 @@ describe("GrafanaOperatorResources alerting (#3156)", () => {
349
356
  expect(() => operatorPolicyRoute({ receiver: "x", routes: [{ matchers: ["nonsense"] }] })).toThrow(/cannot read the route matcher/);
350
357
  });
351
358
 
359
+ test("policyRoutes writes each child route as a GrafanaNotificationPolicyRoute the policy selects by label", () => {
360
+ const docs = ops(alerting, { policyRoutes: true, labels: { team: "sre" } });
361
+ const root = docs.find((d) => d.kind === "GrafanaNotificationPolicy")!;
362
+ const label = { "grafana.chant.dev/policy": "grafana-notification-policy" };
363
+ expect(root.spec.route).toEqual({ receiver: "oncall", group_by: ["alertname"], routeSelector: { matchLabels: label } });
364
+ const routes = docs.filter((d) => d.kind === "GrafanaNotificationPolicyRoute");
365
+ expect(routes.map((d) => d.metadata.name)).toEqual(["grafana-notification-policy-route-1"]);
366
+ expect(routes[0].metadata.labels).toEqual({ team: "sre", ...label });
367
+ expect(routes[0].spec).toEqual({
368
+ receiver: "tickets",
369
+ object_matchers: [["severity", "=", "ticket"]],
370
+ mute_time_intervals: ["weekends"],
371
+ routes: [{ object_matchers: [["team", "=", "a"]] }],
372
+ });
373
+ expect(routes[0].spec.instanceSelector).toBeUndefined();
374
+ });
375
+
376
+ test("a policy with no child routes stays one resource under policyRoutes", () => {
377
+ const docs = ops([oncall, new NotificationPolicy({ receiver: oncall })], { policyRoutes: true });
378
+ expect(docs.some((d) => d.kind === "GrafanaNotificationPolicyRoute")).toBe(false);
379
+ });
380
+
381
+ test("operatorRouteSpec gives a child the policy's receiver when it has none", () => {
382
+ expect(operatorRouteSpec(new NotificationPolicy({ receiver: "oncall", routes: [{ matchers: ["a=\"b\""] }] }))).toEqual({ receiver: "oncall", object_matchers: [["a", "=", "b"]] });
383
+ expect(() => operatorRouteSpec(new NotificationPolicy({ receiver: "oncall" }))).toThrow(/no child route/);
384
+ });
385
+
352
386
  test("a build with no alerting writes none, and options reach the alerting resources", () => {
353
387
  expect(ops([prometheus]).map((d) => d.kind)).toEqual(["GrafanaDatasource"]);
354
388
  const docs = ops([weekends], { allowCrossNamespaceImport: true, resyncPeriod: "5m", namespace: "obs", name: "obs" });
package/src/k8s.ts CHANGED
@@ -46,6 +46,7 @@ import {
46
46
  GrafanaLibraryPanel,
47
47
  GrafanaMuteTiming,
48
48
  GrafanaNotificationPolicy,
49
+ GrafanaNotificationPolicyRoute,
49
50
  GrafanaNotificationTemplate,
50
51
  } from "@intentius/chant-lexicon-k8s/generated/index";
51
52
  import {
@@ -275,6 +276,16 @@ export interface GrafanaOperatorResourcesProps {
275
276
  * contact point has such a reference.
276
277
  */
277
278
  secretName?: string;
279
+ /**
280
+ * Write each direct child route of a notification policy as a
281
+ * `GrafanaNotificationPolicyRoute` of its own, which the policy merges in
282
+ * through `spec.route.routeSelector` (the operator allows `routes` or
283
+ * `routeSelector`, not both). The routes carry the label
284
+ * `grafana.chant.dev/policy: <policy resource name>`, which the selector
285
+ * matches. A route's own nested routes stay inline in its `spec.routes`.
286
+ * Defaults to false: the whole tree inline in `spec.route`.
287
+ */
288
+ policyRoutes?: boolean;
278
289
  }
279
290
 
280
291
  type OperatorEntity = (
@@ -285,6 +296,7 @@ type OperatorEntity = (
285
296
  | InstanceType<typeof GrafanaAlertRuleGroup>
286
297
  | InstanceType<typeof GrafanaContactPoint>
287
298
  | InstanceType<typeof GrafanaNotificationPolicy>
299
+ | InstanceType<typeof GrafanaNotificationPolicyRoute>
288
300
  | InstanceType<typeof GrafanaMuteTiming>
289
301
  | InstanceType<typeof GrafanaNotificationTemplate>
290
302
  ) &
@@ -350,8 +362,15 @@ export function operatorDatasource(
350
362
 
351
363
  // ── Alerting (#3156) ──────────────────────────────────────────────────
352
364
 
353
- /** A setting that is wholly a reference to a variable: `${NAME}`, `$__env{NAME}` or `$NAME`. */
354
- const WHOLE_VARIABLE = /^(?:\$\{([A-Za-z_][A-Za-z0-9_]*)\}|\$__env\{([A-Za-z_][A-Za-z0-9_]*)\}|\$([A-Za-z_][A-Za-z0-9_]*))$/;
365
+ /** The label a `GrafanaNotificationPolicyRoute` carries for the policy whose `routeSelector` merges it in. */
366
+ export const POLICY_ROUTE_LABEL = "grafana.chant.dev/policy";
367
+
368
+ /**
369
+ * A setting that is wholly a reference to a variable: `${NAME}`, `$__env{NAME}` or `$NAME`.
370
+ * The bare `$labels`, `$value` and `$values` are alert template variables, not environment
371
+ * variables, so a setting of exactly that text is left as written.
372
+ */
373
+ const WHOLE_VARIABLE = /^(?:\$\{([A-Za-z_][A-Za-z0-9_]*)\}|\$__env\{([A-Za-z_][A-Za-z0-9_]*)\}|\$(?!(?:labels|values?)$)([A-Za-z_][A-Za-z0-9_]*))$/;
355
374
  /** A braced reference inside longer text, which `valuesFrom` cannot substitute into. */
356
375
  const EMBEDDED_VARIABLE = /\$\{[A-Za-z_][A-Za-z0-9_]*\}|\$__env\{[^}]*\}|\$__(?:file|vault)\{[^}]*\}/;
357
376
 
@@ -502,6 +521,19 @@ export function operatorPolicy(policy: NotificationPolicyEntity): Record<string,
502
521
  return operatorPolicyRoute(notificationPolicyJson(policy));
503
522
  }
504
523
 
524
+ /**
525
+ * `GrafanaNotificationPolicyRoute.spec` for a policy declaration holding the
526
+ * route as its one child: the child's fields, with `receiver` always set
527
+ * (the CRD requires it, so a child that inherits it gets the policy's).
528
+ */
529
+ export function operatorRouteSpec(policy: NotificationPolicyEntity): Record<string, unknown> {
530
+ const tree = operatorPolicy(policy);
531
+ const routes = tree.routes as Array<Record<string, unknown>> | undefined;
532
+ const child = routes?.[0];
533
+ if (!child) throw new Error("grafana: the policy has no child route to write as a GrafanaNotificationPolicyRoute");
534
+ return { ...child, receiver: child.receiver ?? tree.receiver };
535
+ }
536
+
505
537
  /** `GrafanaMuteTiming.spec.time_intervals` for a mute timing. */
506
538
  export function operatorTimeIntervals(timing: MuteTimingEntity): Array<Record<string, unknown>> {
507
539
  return muteTimingJson(timing).time_intervals;
@@ -533,7 +565,9 @@ export function operatorTemplate(template: NotificationTemplateEntity): string {
533
565
  * a `GrafanaContactPoint` per contact point, `<name>-contact-point-<name>`,
534
566
  * whose `${NAME}` secrets go to each receiver's `valuesFrom`; a
535
567
  * `GrafanaNotificationPolicy`, `<name>-notification-policy`, with its
536
- * nested routes inline; a `GrafanaMuteTiming` per mute timing and a
568
+ * nested routes inline (or, with `policyRoutes`, one
569
+ * `GrafanaNotificationPolicyRoute` per direct child route, selected by
570
+ * label); a `GrafanaMuteTiming` per mute timing and a
537
571
  * `GrafanaNotificationTemplate` per template.
538
572
  *
539
573
  * Every resource carries `instanceSelector`. Export the result from a k8s
@@ -650,9 +684,27 @@ export const GrafanaOperatorResources = Composite<GrafanaOperatorResourcesProps,
650
684
  }
651
685
  for (const policy of alerting.policies ?? []) {
652
686
  const name = unique(`${prefix}-notification-policy`);
687
+ const tree = operatorPolicyRoute(policy);
688
+ const children = Array.isArray(tree.routes) ? (tree.routes as Array<Record<string, unknown>>) : [];
689
+ if (props.policyRoutes && children.length > 0) {
690
+ const { routes: _inline, ...root } = tree;
691
+ const selector = { [POLICY_ROUTE_LABEL]: name };
692
+ members[memberName(name, prefix)] = new GrafanaNotificationPolicy({
693
+ metadata: metadata(name),
694
+ spec: { ...common, route: { ...root, routeSelector: { matchLabels: selector } } },
695
+ }) as OperatorEntity;
696
+ children.forEach((child, i) => {
697
+ const routeName = unique(`${prefix}-notification-policy-route-${i + 1}`);
698
+ members[memberName(routeName, prefix)] = new GrafanaNotificationPolicyRoute({
699
+ metadata: { ...metadata(routeName), labels: { ...(props.labels ?? {}), ...selector } },
700
+ spec: { ...child, receiver: child.receiver ?? tree.receiver },
701
+ }) as OperatorEntity;
702
+ });
703
+ continue;
704
+ }
653
705
  members[memberName(name, prefix)] = new GrafanaNotificationPolicy({
654
706
  metadata: metadata(name),
655
- spec: { ...common, route: operatorPolicyRoute(policy) },
707
+ spec: { ...common, route: tree },
656
708
  }) as OperatorEntity;
657
709
  }
658
710
  for (const t of alerting.muteTimes ?? []) {