@intentius/chant-lexicon-grafana 0.105.1 → 0.106.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/detect.d.ts +8 -0
- package/dist/detect.d.ts.map +1 -1
- package/dist/import/embedded.d.ts.map +1 -1
- package/dist/import/operator.d.ts +53 -0
- package/dist/import/operator.d.ts.map +1 -0
- package/dist/integrity.json +3 -3
- package/dist/k8s.d.ts +69 -11
- package/dist/k8s.d.ts.map +1 -1
- package/dist/manifest.json +1 -1
- package/dist/plugin.d.ts.map +1 -1
- package/dist/skills/chant-grafana-provisioning.md +1 -1
- package/package.json +2 -2
- package/src/detect.ts +21 -0
- package/src/import/embedded.test.ts +9 -1
- package/src/import/embedded.ts +5 -6
- package/src/import/operator.test.ts +213 -0
- package/src/import/operator.ts +228 -0
- package/src/k8s.test.ts +156 -1
- package/src/k8s.ts +271 -10
- package/src/plugin.ts +2 -1
- package/src/skills/chant-grafana-provisioning.md +1 -1
package/README.md
CHANGED
|
@@ -61,6 +61,6 @@ GRAF101 and GRAF102 compare dashboards with the datasources declared in the same
|
|
|
61
61
|
## Plain-data API
|
|
62
62
|
|
|
63
63
|
- `buildGrafana(entities)` and `grafanaFiles(entities)` render every file by path, for embedding in another lexicon's output (a ConfigMap, a volume).
|
|
64
|
-
- `GrafanaConfigMaps` and `grafanaVolumes` from `@intentius/chant-lexicon-grafana/k8s` deliver dashboards and provisioning to Kubernetes as ConfigMaps labelled for the Grafana Helm chart's sidecar (`grafana_dashboard`, `grafana_datasource`), or mounted into a plain Grafana Deployment. `GrafanaOperatorResources` from the same subpath writes `GrafanaDashboard`, `GrafanaDatasource`, `GrafanaFolder` and `GrafanaLibraryPanel` resources for a Grafana the Grafana Operator runs.
|
|
64
|
+
- `GrafanaConfigMaps` and `grafanaVolumes` from `@intentius/chant-lexicon-grafana/k8s` deliver dashboards and provisioning to Kubernetes as ConfigMaps labelled for the Grafana Helm chart's sidecar (`grafana_dashboard`, `grafana_datasource`), or mounted into a plain Grafana Deployment. `GrafanaOperatorResources` from the same subpath writes `GrafanaDashboard`, `GrafanaDatasource`, `GrafanaFolder` and `GrafanaLibraryPanel` resources, and `GrafanaAlertRuleGroup`, `GrafanaContactPoint`, `GrafanaNotificationPolicy`, `GrafanaMuteTiming` and `GrafanaNotificationTemplate` resources for the alerting, for a Grafana the Grafana Operator runs.
|
|
65
65
|
- `renderDashboard(dashboard)` and `dashboardJson(dashboard)` render one dashboard.
|
|
66
66
|
- `validateGrafanaOutput({ dashboards, datasources })` and `validateDashboardSchema(json)`, from `@intentius/chant-lexicon-grafana/validation`, run the checks without a build. They are not on the package root, so declaring dashboards does not load them; ajv loads the first time a schema is checked.
|
package/dist/detect.d.ts
CHANGED
|
@@ -40,6 +40,14 @@ export declare function looksLikeDashboardProvisioning(data: unknown): data is {
|
|
|
40
40
|
* of `name` and `rules` with `expr`) is not mistaken for one.
|
|
41
41
|
*/
|
|
42
42
|
export declare function looksLikeAlertingProvisioning(data: unknown): data is Record<string, unknown>;
|
|
43
|
+
/**
|
|
44
|
+
* The `spec` of a Grafana Operator alerting resource (`GrafanaAlertRuleGroup`,
|
|
45
|
+
* `GrafanaContactPoint`, `GrafanaNotificationPolicy`, `GrafanaMuteTiming`,
|
|
46
|
+
* `GrafanaNotificationTemplate`) as the k8s importer offers it (#3538): every
|
|
47
|
+
* operator resource's spec has an `instanceSelector`, beside the field that
|
|
48
|
+
* holds the alerting content.
|
|
49
|
+
*/
|
|
50
|
+
export declare function looksLikeOperatorAlertingSpec(data: unknown): boolean;
|
|
43
51
|
/**
|
|
44
52
|
* Template detection for `chant import` and friends: dashboard JSON in any
|
|
45
53
|
* of the shapes above (v2 included, #2947), or a datasource, dashboard or
|
package/dist/detect.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"detect.d.ts","sourceRoot":"","sources":["../src/detect.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAMH,uFAAuF;AACvF,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAGjF;AAED,mFAAmF;AACnF,wBAAgB,4BAA4B,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAE3F;AAED,8GAA8G;AAC9G,wBAAgB,0BAA0B,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI,IAAI;IAAE,UAAU,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,WAAW,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAAE,CAQ1I;AAED,2GAA2G;AAC3G,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO,CAG3D;AAED,2EAA2E;AAC3E,wBAAgB,6BAA6B,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI,IAAI;IAAE,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAAC,IAAI,EAAE,OAAO,CAAA;CAAE,CAE1H;AAED,oGAAoG;AACpG,wBAAgB,+BAA+B,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI,IAAI;IAAE,UAAU,EAAE,OAAO,CAAC;IAAC,WAAW,CAAC,EAAE,OAAO,EAAE,CAAC;IAAC,iBAAiB,CAAC,EAAE,OAAO,EAAE,CAAC;IAAC,KAAK,CAAC,EAAE,OAAO,CAAA;CAAE,CAEvK;AAED,0EAA0E;AAC1E,wBAAgB,8BAA8B,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI,IAAI;IAAE,UAAU,EAAE,OAAO,CAAC;IAAC,SAAS,EAAE,OAAO,EAAE,CAAA;CAAE,CAEnH;AAuBD;;;;;;GAMG;AACH,wBAAgB,6BAA6B,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAO5F;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO,
|
|
1
|
+
{"version":3,"file":"detect.d.ts","sourceRoot":"","sources":["../src/detect.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAMH,uFAAuF;AACvF,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAGjF;AAED,mFAAmF;AACnF,wBAAgB,4BAA4B,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAE3F;AAED,8GAA8G;AAC9G,wBAAgB,0BAA0B,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI,IAAI;IAAE,UAAU,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,WAAW,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAAE,CAQ1I;AAED,2GAA2G;AAC3G,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO,CAG3D;AAED,2EAA2E;AAC3E,wBAAgB,6BAA6B,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI,IAAI;IAAE,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAAC,IAAI,EAAE,OAAO,CAAA;CAAE,CAE1H;AAED,oGAAoG;AACpG,wBAAgB,+BAA+B,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI,IAAI;IAAE,UAAU,EAAE,OAAO,CAAC;IAAC,WAAW,CAAC,EAAE,OAAO,EAAE,CAAC;IAAC,iBAAiB,CAAC,EAAE,OAAO,EAAE,CAAC;IAAC,KAAK,CAAC,EAAE,OAAO,CAAA;CAAE,CAEvK;AAED,0EAA0E;AAC1E,wBAAgB,8BAA8B,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI,IAAI;IAAE,UAAU,EAAE,OAAO,CAAC;IAAC,SAAS,EAAE,OAAO,EAAE,CAAA;CAAE,CAEnH;AAuBD;;;;;;GAMG;AACH,wBAAgB,6BAA6B,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAO5F;AAED;;;;;;GAMG;AACH,wBAAgB,6BAA6B,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO,CAWpE;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO,CAYrD"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"embedded.d.ts","sourceRoot":"","sources":["../../src/import/embedded.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,KAAK,EAAE,uBAAuB,EAAkB,MAAM,kCAAkC,CAAC;AAOhG,eAAO,MAAM,iBAAiB,EAAE,
|
|
1
|
+
{"version":3,"file":"embedded.d.ts","sourceRoot":"","sources":["../../src/import/embedded.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,KAAK,EAAE,uBAAuB,EAAkB,MAAM,kCAAkC,CAAC;AAOhG,eAAO,MAAM,iBAAiB,EAAE,uBAwC/B,CAAC"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Grafana Operator alerting resources embedded in a k8s manifest, for
|
|
3
|
+
* `chant import` (#3538).
|
|
4
|
+
*
|
|
5
|
+
* `GrafanaOperatorResources` (#3156) writes `GrafanaAlertRuleGroup`,
|
|
6
|
+
* `GrafanaContactPoint`, `GrafanaNotificationPolicy`, `GrafanaMuteTiming` and
|
|
7
|
+
* `GrafanaNotificationTemplate` from grafana alerting declarations. The k8s
|
|
8
|
+
* parser offers the one field of each that holds the alerting content
|
|
9
|
+
* (`spec.rules`, `spec.receivers`, `spec.route`, `spec.time_intervals`,
|
|
10
|
+
* `spec.template`) with the whole `spec` as the document. This importer
|
|
11
|
+
* reads that spec back into the provisioning-file shape, which the alerting
|
|
12
|
+
* importer turns into declarations, and the k8s value becomes a call of the
|
|
13
|
+
* matching function in `@intentius/chant-lexicon-grafana/k8s` over the
|
|
14
|
+
* declaration, so a build gives the same field again.
|
|
15
|
+
*
|
|
16
|
+
* The reverse of what `GrafanaOperatorResources` writes:
|
|
17
|
+
*
|
|
18
|
+
* - a rule: the CRD's camel case (`missingSeriesEvalsToResolve`,
|
|
19
|
+
* `notificationSettings`) goes back to the file's snake case, the
|
|
20
|
+
* `__dashboardUid__` and `__panelId__` annotations go back to
|
|
21
|
+
* `dashboardUid` and `panelId`, and the values the CRD requires and the
|
|
22
|
+
* file leaves to Grafana's defaults (`for: 0s`, `noDataState: NoData`,
|
|
23
|
+
* `execErrState: Alerting`) are left out;
|
|
24
|
+
* - a receiver: each `valuesFrom` entry becomes `${NAME}` at its
|
|
25
|
+
* `targetPath` in the settings, `NAME` being the Secret key. The Secret's
|
|
26
|
+
* name is not part of a declaration, so a contact point that reads a
|
|
27
|
+
* Secret is written with a small module holding
|
|
28
|
+
* `operatorReceivers(contactPoint, "<secret>")`;
|
|
29
|
+
* - the policy tree: `object_matchers` go back to Alertmanager `matchers`
|
|
30
|
+
* strings where each can be written as one.
|
|
31
|
+
*
|
|
32
|
+
* A rule group's folder is a title in a declaration and a `GrafanaFolder`
|
|
33
|
+
* name (`folderRef`) in the resource, and the resource does not hold the
|
|
34
|
+
* title, so the declaration's `folder` is the reference, with a warning.
|
|
35
|
+
*/
|
|
36
|
+
import type { EmbeddedContentImporter } from "@intentius/chant/import/embedded";
|
|
37
|
+
type Json = Record<string, unknown>;
|
|
38
|
+
/** One entry of `GrafanaAlertRuleGroup.spec.rules` as a rule of the provisioning file. */
|
|
39
|
+
export declare function provisionedRule(rule: Json): Json;
|
|
40
|
+
/**
|
|
41
|
+
* `GrafanaContactPoint.spec.receivers` as the provisioning file's receivers,
|
|
42
|
+
* each `valuesFrom` entry put back as `${NAME}` in the settings, and the name
|
|
43
|
+
* of the Secret they read (one Secret, as `GrafanaOperatorResources` writes).
|
|
44
|
+
*/
|
|
45
|
+
export declare function provisionedReceivers(receivers: unknown[], where: string): {
|
|
46
|
+
receivers: Json[];
|
|
47
|
+
secretName?: string;
|
|
48
|
+
};
|
|
49
|
+
/** A route of the policy tree as the provisioning file holds it: `object_matchers` as `matchers` strings, in every nested route. */
|
|
50
|
+
export declare function provisionedRoute(route: Json): Json;
|
|
51
|
+
export declare const operatorImporter: EmbeddedContentImporter;
|
|
52
|
+
export {};
|
|
53
|
+
//# sourceMappingURL=operator.d.ts.map
|
|
@@ -0,0 +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"}
|
package/dist/integrity.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"algorithm": "sha256",
|
|
3
3
|
"artifacts": {
|
|
4
|
-
"manifest.json": "
|
|
4
|
+
"manifest.json": "06345c465338218becce11ad5a3cf5e8c130d367b0eb9f8044886d2a5987489a",
|
|
5
5
|
"meta.json": "330a1598c3b35ae62267e886a3416a494b0ebe9f1d2e2379253787d0a0bcb32e",
|
|
6
6
|
"types/index.d.ts": "ad385264290f2486f7a54be205684de8415a25b74eee5b809ca625ae19fb08da",
|
|
7
7
|
"rules/grafana-ast.ts": "51f4fac423f7a5c6d6f577074e95b4a70d4ab0a05883a86e04970a50c63d92e8",
|
|
@@ -27,9 +27,9 @@
|
|
|
27
27
|
"rules/graf118.ts": "e4ad3c5b944185139331a1f30488c6107a87f37872a0d52b7afc03d9a2ed6125",
|
|
28
28
|
"rules/grafana-helpers.ts": "0a538daafb10f32077dd6cda4c03b541f703b0a10abc51bfd241eae9365c9be6",
|
|
29
29
|
"skills/chant-grafana.md": "6a7f4ba6d8a40d339fad892c612131370ef0c2595547860ee718801bfa893163",
|
|
30
|
-
"skills/chant-grafana-provisioning.md": "
|
|
30
|
+
"skills/chant-grafana-provisioning.md": "1c7f473ef4e1f9c2debed62833e4f0448ed0fef9f01c83fbbc94299715ed2602",
|
|
31
31
|
"skills/chant-grafana-alerting.md": "d9648dc14c848851bdccbe069dac54ad06115066f9466c90c4137d07dde6d4ad",
|
|
32
32
|
"skills/chant-grafana-operations.md": "4e665f80292ab32bde6d77940cc3f3923678c58e6db0b80613489050d2cb665b"
|
|
33
33
|
},
|
|
34
|
-
"composite": "
|
|
34
|
+
"composite": "4a36d87b528d23995dab52878e7e42c4429aa9611a9b9bef2c133e9c4e2c7ba5"
|
|
35
35
|
}
|
package/dist/k8s.d.ts
CHANGED
|
@@ -23,16 +23,21 @@
|
|
|
23
23
|
* A Grafana run by the Grafana Operator reads custom resources instead
|
|
24
24
|
* (#3015): `GrafanaOperatorResources` writes a `GrafanaDashboard` per
|
|
25
25
|
* dashboard, a `GrafanaDatasource` per datasource, a `GrafanaFolder` per
|
|
26
|
-
* folder and a `GrafanaLibraryPanel` per library panel (#3186),
|
|
27
|
-
*
|
|
28
|
-
*
|
|
26
|
+
* folder and a `GrafanaLibraryPanel` per library panel (#3186), and for
|
|
27
|
+
* alerting (#3156) a `GrafanaAlertRuleGroup` per rule group, a
|
|
28
|
+
* `GrafanaContactPoint` per contact point, a `GrafanaNotificationPolicy`, a
|
|
29
|
+
* `GrafanaMuteTiming` per mute timing and a `GrafanaNotificationTemplate` per
|
|
30
|
+
* template, all `grafana.integreatly.org/v1beta1` as the k8s lexicon types
|
|
31
|
+
* them from the operator's CRDs (pinned in its `crd-sources.ts`).
|
|
29
32
|
*
|
|
30
33
|
* This module is the only part of the grafana lexicon that loads the k8s
|
|
31
34
|
* lexicon; nothing else imports it.
|
|
32
35
|
*/
|
|
33
36
|
import { type CompositeInstance } from "@intentius/chant/composite";
|
|
34
37
|
import type { Declarable } from "@intentius/chant/declarable";
|
|
35
|
-
import { ConfigMap, GrafanaDashboard, GrafanaDatasource, GrafanaFolder, GrafanaLibraryPanel } from "@intentius/chant-lexicon-k8s/generated/index";
|
|
38
|
+
import { ConfigMap, GrafanaAlertRuleGroup, GrafanaContactPoint, GrafanaDashboard, GrafanaDatasource, GrafanaFolder, GrafanaLibraryPanel, GrafanaMuteTiming, GrafanaNotificationPolicy, GrafanaNotificationTemplate } from "@intentius/chant-lexicon-k8s/generated/index";
|
|
39
|
+
import { type ProvisionedAlertRule, type ProvisionedContactPoint } from "./alerting-build.js";
|
|
40
|
+
import type { AlertRuleGroupEntity, ContactPointEntity, MuteTimingEntity, NotificationPolicyEntity, NotificationTemplateEntity } from "./alerting.js";
|
|
36
41
|
import { type ProvisionedDatasource } from "./build.js";
|
|
37
42
|
/** The label the Grafana Helm chart's sidecar looks for on dashboard ConfigMaps (`sidecar.dashboards.label`). */
|
|
38
43
|
export declare const SIDECAR_DASHBOARD_LABEL = "grafana_dashboard";
|
|
@@ -148,15 +153,18 @@ export interface GrafanaOperatorResourcesProps {
|
|
|
148
153
|
*/
|
|
149
154
|
dashboardSource?: "json" | "configMap";
|
|
150
155
|
/**
|
|
151
|
-
* The Secret that datasource secrets come from. A
|
|
152
|
-
* as `${NAME}` or `$__env{NAME}` (the way file
|
|
153
|
-
* environment) becomes a `spec.valuesFrom` entry
|
|
154
|
-
* this Secret, which the operator substitutes for
|
|
155
|
-
*
|
|
156
|
+
* The Secret that datasource and contact point secrets come from. A
|
|
157
|
+
* datasource field written as `${NAME}` or `$__env{NAME}` (the way file
|
|
158
|
+
* provisioning reads the environment) becomes a `spec.valuesFrom` entry
|
|
159
|
+
* reading key `NAME` of this Secret, which the operator substitutes for
|
|
160
|
+
* `${NAME}`. A contact point setting that is entirely such a reference
|
|
161
|
+
* becomes a `valuesFrom` entry of its receiver, which the operator writes
|
|
162
|
+
* into the setting at `targetPath`. Required when any datasource or
|
|
163
|
+
* contact point has such a reference.
|
|
156
164
|
*/
|
|
157
165
|
secretName?: string;
|
|
158
166
|
}
|
|
159
|
-
type OperatorEntity = (InstanceType<typeof GrafanaDashboard> | InstanceType<typeof GrafanaDatasource> | InstanceType<typeof GrafanaFolder> | InstanceType<typeof GrafanaLibraryPanel>) & Declarable;
|
|
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;
|
|
160
168
|
export type GrafanaOperatorResourcesMembers = Record<string, OperatorEntity>;
|
|
161
169
|
export type GrafanaOperatorResourcesInstance = CompositeInstance<GrafanaOperatorResourcesMembers> & GrafanaOperatorResourcesMembers;
|
|
162
170
|
/**
|
|
@@ -170,6 +178,48 @@ export declare function operatorDatasource(ds: ProvisionedDatasource, secretName
|
|
|
170
178
|
datasource: Record<string, unknown>;
|
|
171
179
|
valuesFrom: Array<Record<string, unknown>>;
|
|
172
180
|
};
|
|
181
|
+
/**
|
|
182
|
+
* One contact point receiver's settings for `spec.receivers[].settings`, and
|
|
183
|
+
* the `valuesFrom` entries for the secrets in it. A setting that is wholly
|
|
184
|
+
* `${NAME}` (or `$__env{NAME}`, `$NAME`) is taken out of `settings` and read
|
|
185
|
+
* from key `NAME` of `secretName` into the same path by the operator, whose
|
|
186
|
+
* `targetPath` is a dotted path inside the settings. A reference inside
|
|
187
|
+
* longer text, or a `$__file` or `$__vault` one, is an error: the operator
|
|
188
|
+
* can only replace a whole value.
|
|
189
|
+
*/
|
|
190
|
+
export declare function operatorReceiverSettings(where: string, settings: Record<string, unknown>, secretName: string | undefined): {
|
|
191
|
+
settings: Record<string, unknown>;
|
|
192
|
+
valuesFrom: Array<Record<string, unknown>>;
|
|
193
|
+
};
|
|
194
|
+
/** One contact point as `GrafanaContactPoint.spec`: its name, and its receivers with their secrets in `valuesFrom`. */
|
|
195
|
+
export declare function operatorContactPoint(cp: ProvisionedContactPoint, secretName: string | undefined): Record<string, unknown>;
|
|
196
|
+
/**
|
|
197
|
+
* One rule as an entry of `GrafanaAlertRuleGroup.spec.rules`. The CRD spells
|
|
198
|
+
* two fields in camel case (`missingSeriesEvalsToResolve`,
|
|
199
|
+
* `notificationSettings`), requires `for`, `noDataState` and `execErrState`
|
|
200
|
+
* (Grafana's defaults fill them), and has no use for `dashboardUid` and
|
|
201
|
+
* `panelId`, which it reads from the `__dashboardUid__` and `__panelId__`
|
|
202
|
+
* annotations.
|
|
203
|
+
*/
|
|
204
|
+
export declare function operatorRule(rule: ProvisionedAlertRule): Record<string, unknown>;
|
|
205
|
+
/** One policy tree as `GrafanaNotificationPolicy.spec.route`. `orgId` is the operator's to set, so it is left out. */
|
|
206
|
+
export declare function operatorPolicyRoute(policy: Record<string, unknown>): Record<string, unknown>;
|
|
207
|
+
/**
|
|
208
|
+
* The fields `chant import` writes into an imported Grafana Operator
|
|
209
|
+
* resource (#3538): each takes the alerting declaration the field was read
|
|
210
|
+
* into and gives the field `GrafanaOperatorResources` writes for it, so the
|
|
211
|
+
* resource builds back to what was imported.
|
|
212
|
+
*/
|
|
213
|
+
/** `GrafanaAlertRuleGroup.spec.rules` for a rule group. */
|
|
214
|
+
export declare function operatorRules(group: AlertRuleGroupEntity): Array<Record<string, unknown>>;
|
|
215
|
+
/** `GrafanaContactPoint.spec.receivers` for a contact point, its `${NAME}` secrets read from `secretName`. */
|
|
216
|
+
export declare function operatorReceivers(cp: ContactPointEntity, secretName?: string): Array<Record<string, unknown>>;
|
|
217
|
+
/** `GrafanaNotificationPolicy.spec.route` for a policy tree. */
|
|
218
|
+
export declare function operatorPolicy(policy: NotificationPolicyEntity): Record<string, unknown>;
|
|
219
|
+
/** `GrafanaMuteTiming.spec.time_intervals` for a mute timing. */
|
|
220
|
+
export declare function operatorTimeIntervals(timing: MuteTimingEntity): Array<Record<string, unknown>>;
|
|
221
|
+
/** `GrafanaNotificationTemplate.spec.template` for a template. */
|
|
222
|
+
export declare function operatorTemplate(template: NotificationTemplateEntity): string;
|
|
173
223
|
/**
|
|
174
224
|
* The Grafana Operator's custom resources for a set of grafana declarations
|
|
175
225
|
* (`grafana.integreatly.org/v1beta1`, Grafana Operator v5.25.0):
|
|
@@ -184,7 +234,15 @@ export declare function operatorDatasource(ds: ProvisionedDatasource, secretName
|
|
|
184
234
|
* - a `GrafanaDashboard` per dashboard, `<name>-dashboard-<uid>`, holding
|
|
185
235
|
* `dashboardJson(dashboard)` in `spec.json` and naming its folder with
|
|
186
236
|
* `folderRef`;
|
|
187
|
-
* - a `GrafanaDatasource` per datasource, `<name>-datasource-<uid
|
|
237
|
+
* - a `GrafanaDatasource` per datasource, `<name>-datasource-<uid>`;
|
|
238
|
+
* - for the alerting the build declares: a `GrafanaAlertRuleGroup` per rule
|
|
239
|
+
* group, `<name>-rule-group-<folder>-<group>`, in the `GrafanaFolder` whose
|
|
240
|
+
* title is the group's `folder` (one is written when no folder has it);
|
|
241
|
+
* a `GrafanaContactPoint` per contact point, `<name>-contact-point-<name>`,
|
|
242
|
+
* whose `${NAME}` secrets go to each receiver's `valuesFrom`; a
|
|
243
|
+
* `GrafanaNotificationPolicy`, `<name>-notification-policy`, with its
|
|
244
|
+
* nested routes inline; a `GrafanaMuteTiming` per mute timing and a
|
|
245
|
+
* `GrafanaNotificationTemplate` per template.
|
|
188
246
|
*
|
|
189
247
|
* Every resource carries `instanceSelector`. Export the result from a k8s
|
|
190
248
|
* build root, beside or instead of `GrafanaConfigMaps`.
|
package/dist/k8s.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"k8s.d.ts","sourceRoot":"","sources":["../src/k8s.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"k8s.d.ts","sourceRoot":"","sources":["../src/k8s.ts"],"names":[],"mappings":"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"}
|
package/dist/manifest.json
CHANGED
package/dist/plugin.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;
|
|
1
|
+
{"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAoD9D;;;;;;;;GAQG;AACH,eAAO,MAAM,aAAa,EAAE,aA6I3B,CAAC"}
|
|
@@ -25,7 +25,7 @@ docker run -p 3000:3000 \
|
|
|
25
25
|
|
|
26
26
|
## Kubernetes
|
|
27
27
|
|
|
28
|
-
Use `GrafanaConfigMaps({ entities, namespace })` from `@intentius/chant-lexicon-grafana/k8s` in a k8s build root. It writes one ConfigMap per dashboard labelled `grafana_dashboard: "1"`, with the folder in the `k8s-sidecar-target-directory` annotation. The datasource file goes in a ConfigMap labelled `grafana_datasource: "1"`, and the provider file in a third. The Grafana Helm chart's sidecar picks these up as they are. For a Grafana without the sidecar, spread `grafanaVolumes({ entities })`'s `volumes` and `volumeMounts` into the Deployment: provisioning goes under `/etc/grafana/provisioning`, and each dashboard folder gets a projected volume of its own. For a Grafana the Grafana Operator runs, use `GrafanaOperatorResources({ entities, namespace, instanceSelector })` instead: a `GrafanaFolder` per folder, a `GrafanaLibraryPanel` per library panel the dashboards place, a `GrafanaDashboard` per dashboard (JSON in `spec.json`, folder by `folderRef`) and a `
|
|
28
|
+
Use `GrafanaConfigMaps({ entities, namespace })` from `@intentius/chant-lexicon-grafana/k8s` in a k8s build root. It writes one ConfigMap per dashboard labelled `grafana_dashboard: "1"`, with the folder in the `k8s-sidecar-target-directory` annotation. The datasource file goes in a ConfigMap labelled `grafana_datasource: "1"`, and the provider file in a third. The Grafana Helm chart's sidecar picks these up as they are. For a Grafana without the sidecar, spread `grafanaVolumes({ entities })`'s `volumes` and `volumeMounts` into the Deployment: provisioning goes under `/etc/grafana/provisioning`, and each dashboard folder gets a projected volume of its own. For a Grafana the Grafana Operator runs, use `GrafanaOperatorResources({ entities, namespace, instanceSelector })` instead: a `GrafanaFolder` per folder, a `GrafanaLibraryPanel` per library panel the dashboards place, a `GrafanaDashboard` per dashboard (JSON in `spec.json`, folder by `folderRef`) a `GrafanaDatasource` per datasource, and for alerting a `GrafanaAlertRuleGroup` per rule group, a `GrafanaContactPoint` per contact point, a `GrafanaNotificationPolicy`, a `GrafanaMuteTiming` per mute timing and a `GrafanaNotificationTemplate` per template. `instanceSelector` is required and must match the labels on your `Grafana` resource. A datasource secret, or a contact point setting that is wholly a variable, written `${NAME}` or `$__env{NAME}` needs `secretName`, the Secret holding key `NAME`; contact point secrets go in each receiver's `valuesFrom`. `grafanaFiles(entities)` still returns every file by path for any other layout.
|
|
29
29
|
|
|
30
30
|
## Changing where dashboards live
|
|
31
31
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@intentius/chant-lexicon-grafana",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.106.1",
|
|
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.
|
|
86
|
+
"@intentius/chant": "^0.106.1",
|
|
87
87
|
"typescript": "^5.9.3",
|
|
88
88
|
"zod": "^4.3.6"
|
|
89
89
|
}
|
package/src/detect.ts
CHANGED
|
@@ -88,6 +88,26 @@ export function looksLikeAlertingProvisioning(data: unknown): data is Record<str
|
|
|
88
88
|
return data.apiVersion !== undefined || Array.isArray(data.groups);
|
|
89
89
|
}
|
|
90
90
|
|
|
91
|
+
/**
|
|
92
|
+
* The `spec` of a Grafana Operator alerting resource (`GrafanaAlertRuleGroup`,
|
|
93
|
+
* `GrafanaContactPoint`, `GrafanaNotificationPolicy`, `GrafanaMuteTiming`,
|
|
94
|
+
* `GrafanaNotificationTemplate`) as the k8s importer offers it (#3538): every
|
|
95
|
+
* operator resource's spec has an `instanceSelector`, beside the field that
|
|
96
|
+
* holds the alerting content.
|
|
97
|
+
*/
|
|
98
|
+
export function looksLikeOperatorAlertingSpec(data: unknown): boolean {
|
|
99
|
+
if (typeof data !== "object" || data === null || Array.isArray(data)) return false;
|
|
100
|
+
const d = data as Record<string, unknown>;
|
|
101
|
+
if (typeof d.instanceSelector !== "object" || d.instanceSelector === null) return false;
|
|
102
|
+
return (
|
|
103
|
+
(typeof d.name === "string" && Array.isArray(d.rules)) ||
|
|
104
|
+
(typeof d.name === "string" && Array.isArray(d.receivers)) ||
|
|
105
|
+
(typeof d.route === "object" && d.route !== null) ||
|
|
106
|
+
(typeof d.name === "string" && Array.isArray(d.time_intervals)) ||
|
|
107
|
+
(typeof d.name === "string" && typeof d.template === "string")
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
|
|
91
111
|
/**
|
|
92
112
|
* Template detection for `chant import` and friends: dashboard JSON in any
|
|
93
113
|
* of the shapes above (v2 included, #2947), or a datasource, dashboard or
|
|
@@ -95,6 +115,7 @@ export function looksLikeAlertingProvisioning(data: unknown): data is Record<str
|
|
|
95
115
|
*/
|
|
96
116
|
export function detectTemplate(data: unknown): boolean {
|
|
97
117
|
return (
|
|
118
|
+
looksLikeOperatorAlertingSpec(data) ||
|
|
98
119
|
looksLikeDashboard(data) ||
|
|
99
120
|
looksLikeLegacyRowsDashboard(data) ||
|
|
100
121
|
looksLikeDashboardResource(data) ||
|
|
@@ -2,6 +2,7 @@ import { describe, expect, test } from "vitest";
|
|
|
2
2
|
import { embeddedDocument, type EmbeddedContent } from "@intentius/chant/import/embedded";
|
|
3
3
|
import { grafanaPlugin } from "../plugin";
|
|
4
4
|
import { dashboardImporter } from "./embedded";
|
|
5
|
+
import { operatorImporter } from "./operator";
|
|
5
6
|
|
|
6
7
|
const DASHBOARD = JSON.stringify({
|
|
7
8
|
title: "API",
|
|
@@ -22,7 +23,7 @@ const site = (text: string): EmbeddedContent => ({
|
|
|
22
23
|
|
|
23
24
|
describe("dashboard JSON embedded in another lexicon's resource (#2962)", () => {
|
|
24
25
|
test("the plugin registers the importer", () => {
|
|
25
|
-
expect(grafanaPlugin.embeddedImporters?.()).toEqual([dashboardImporter]);
|
|
26
|
+
expect(grafanaPlugin.embeddedImporters?.()).toEqual([dashboardImporter, operatorImporter]);
|
|
26
27
|
});
|
|
27
28
|
|
|
28
29
|
test("matches classic and v2 dashboard JSON; not other text", () => {
|
|
@@ -54,4 +55,11 @@ describe("dashboard JSON embedded in another lexicon's resource (#2962)", () =>
|
|
|
54
55
|
const file = out.files.find((f) => f.path === binding.from);
|
|
55
56
|
expect(file?.content).toContain(`const ${binding.name} = new Dashboard(`);
|
|
56
57
|
});
|
|
58
|
+
|
|
59
|
+
test("panels and queries are written inline, as `chant import dashboard.json` writes them (#3184)", () => {
|
|
60
|
+
const out = dashboardImporter.import(site(DASHBOARD));
|
|
61
|
+
const text = out.files.map((f) => f.content).join("\n");
|
|
62
|
+
expect(text).toContain("new StatPanel(");
|
|
63
|
+
expect(text).not.toMatch(/const panel\d* = new StatPanel/);
|
|
64
|
+
});
|
|
57
65
|
});
|
package/src/import/embedded.ts
CHANGED
|
@@ -45,12 +45,11 @@ export const dashboardImporter: EmbeddedContentImporter = {
|
|
|
45
45
|
const resource = ir.resources.find((r) => r.type === DASHBOARD_RESOURCE_TYPE);
|
|
46
46
|
if (!resource) throw new Error((ir.warnings ?? []).join(" ") || "no dashboard was read");
|
|
47
47
|
const { plan } = resource.properties as unknown as PlanResourceProperties;
|
|
48
|
-
// Core gives the content a directory of its own; the plan's would nest a second one inside it.
|
|
49
|
-
//
|
|
50
|
-
//
|
|
51
|
-
|
|
52
|
-
const
|
|
53
|
-
const { files, exported } = generatePlanModules(flat);
|
|
48
|
+
// Core gives the content a directory of its own; the plan's would nest a second one inside it. Panels and
|
|
49
|
+
// queries stay inline: core lint takes property-kind classes from every lexicon the source imports (#3184), so
|
|
50
|
+
// the host's project need not list grafana.
|
|
51
|
+
const nested = { ...plan, directory: "", main: undefined };
|
|
52
|
+
const { files, exported } = generatePlanModules(nested);
|
|
54
53
|
const dashboard = exported.get("dashboard");
|
|
55
54
|
if (!dashboard) throw new Error("the import declared no dashboard");
|
|
56
55
|
return {
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
import { describe, expect, test } from "vitest";
|
|
2
|
+
import type { EmbeddedContent } from "@intentius/chant/import/embedded";
|
|
3
|
+
import { grafanaPlugin } from "../plugin";
|
|
4
|
+
import { operatorContactPoint, operatorPolicyRoute, operatorRule } from "../k8s";
|
|
5
|
+
import { operatorImporter, provisionedReceivers, provisionedRoute, provisionedRule } from "./operator";
|
|
6
|
+
|
|
7
|
+
const site = (hostType: string, select: string, spec: Record<string, unknown>): EmbeddedContent => ({
|
|
8
|
+
host: "k8s",
|
|
9
|
+
hostType,
|
|
10
|
+
location: `${hostType.split("::").pop()} x spec.${select}`,
|
|
11
|
+
directory: "x",
|
|
12
|
+
document: spec,
|
|
13
|
+
select,
|
|
14
|
+
labels: {},
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
const SELECTOR = { matchLabels: { dashboards: "grafana" } };
|
|
18
|
+
|
|
19
|
+
describe("Grafana Operator alerting embedded in a k8s manifest (#3538)", () => {
|
|
20
|
+
test("the plugin registers the importer", () => {
|
|
21
|
+
expect(grafanaPlugin.embeddedImporters?.()).toContain(operatorImporter);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
test("matches the five kinds at their own field, and nothing else", () => {
|
|
25
|
+
expect(operatorImporter.matches(site("K8s::Grafana::GrafanaAlertRuleGroup", "rules", { rules: [] }))).toBe(true);
|
|
26
|
+
expect(operatorImporter.matches(site("K8s::Grafana::GrafanaContactPoint", "receivers", { receivers: [] }))).toBe(true);
|
|
27
|
+
expect(operatorImporter.matches(site("K8s::Grafana::GrafanaNotificationPolicy", "route", { route: {} }))).toBe(true);
|
|
28
|
+
expect(operatorImporter.matches(site("K8s::Grafana::GrafanaMuteTiming", "time_intervals", { time_intervals: [] }))).toBe(true);
|
|
29
|
+
expect(operatorImporter.matches(site("K8s::Grafana::GrafanaNotificationTemplate", "template", { template: "x" }))).toBe(true);
|
|
30
|
+
expect(operatorImporter.matches(site("K8s::Grafana::GrafanaAlertRuleGroup", "receivers", {}))).toBe(false);
|
|
31
|
+
expect(operatorImporter.matches(site("K8s::Core::ConfigMap", "rules", { rules: [] }))).toBe(false);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
describe("a rule", () => {
|
|
35
|
+
test("goes back to the file's spelling, without the values the CRD requires and Grafana defaults", () => {
|
|
36
|
+
expect(
|
|
37
|
+
provisionedRule({
|
|
38
|
+
uid: "r",
|
|
39
|
+
title: "R",
|
|
40
|
+
condition: "A",
|
|
41
|
+
data: [],
|
|
42
|
+
for: "0s",
|
|
43
|
+
noDataState: "NoData",
|
|
44
|
+
execErrState: "Alerting",
|
|
45
|
+
missingSeriesEvalsToResolve: 3,
|
|
46
|
+
notificationSettings: { receiver: "oncall", group_by: ["a"] },
|
|
47
|
+
annotations: { __dashboardUid__: "dash", __panelId__: "2", summary: "s" },
|
|
48
|
+
}),
|
|
49
|
+
).toEqual({
|
|
50
|
+
uid: "r",
|
|
51
|
+
title: "R",
|
|
52
|
+
condition: "A",
|
|
53
|
+
data: [],
|
|
54
|
+
missing_series_evals_to_resolve: 3,
|
|
55
|
+
notification_settings: { receiver: "oncall", group_by: ["a"] },
|
|
56
|
+
dashboardUid: "dash",
|
|
57
|
+
panelId: 2,
|
|
58
|
+
annotations: { summary: "s" },
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
test("keeps values that are not the defaults, and a record rule's condition is the rule's own", () => {
|
|
63
|
+
const back = provisionedRule({ uid: "r", title: "R", condition: "B", data: [], for: "5m", noDataState: "Alerting", execErrState: "Error" });
|
|
64
|
+
expect(back).toMatchObject({ for: "5m", noDataState: "Alerting", execErrState: "Error", condition: "B" });
|
|
65
|
+
expect(provisionedRule({ uid: "r", title: "R", condition: "A", data: [], record: { metric: "m", from: "A" } })).not.toHaveProperty("condition");
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
test("is the inverse of operatorRule", () => {
|
|
69
|
+
const rule = {
|
|
70
|
+
uid: "r",
|
|
71
|
+
title: "R",
|
|
72
|
+
condition: "A",
|
|
73
|
+
data: [],
|
|
74
|
+
for: "5m",
|
|
75
|
+
labels: { severity: "page" },
|
|
76
|
+
dashboardUid: "dash",
|
|
77
|
+
panelId: 4,
|
|
78
|
+
annotations: { summary: "s" },
|
|
79
|
+
missing_series_evals_to_resolve: 2,
|
|
80
|
+
notification_settings: { receiver: "oncall" },
|
|
81
|
+
};
|
|
82
|
+
expect(provisionedRule(operatorRule(rule))).toEqual(rule);
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
describe("a receiver", () => {
|
|
87
|
+
test("reads each valuesFrom entry back as ${NAME} at its path, and names the Secret", () => {
|
|
88
|
+
const forward = operatorContactPoint(
|
|
89
|
+
{ name: "oncall", receivers: [{ uid: "u", type: "slack", settings: { title: "t", url: "${SLACK_URL}", tlsConfig: { clientKey: "$KEY" } } }] },
|
|
90
|
+
"grafana-secrets",
|
|
91
|
+
);
|
|
92
|
+
const back = provisionedReceivers(forward.receivers as unknown[], "contact point");
|
|
93
|
+
expect(back.secretName).toBe("grafana-secrets");
|
|
94
|
+
expect(back.receivers).toEqual([{ uid: "u", type: "slack", settings: { title: "t", url: "${SLACK_URL}", tlsConfig: { clientKey: "${KEY}" } } }]);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
test("without valuesFrom there is no Secret", () => {
|
|
98
|
+
expect(provisionedReceivers([{ uid: "m", type: "email", settings: { addresses: "a@example.com" } }], "cp")).toEqual({
|
|
99
|
+
receivers: [{ uid: "m", type: "email", settings: { addresses: "a@example.com" } }],
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
test("refuses what a ${NAME} reference cannot say", () => {
|
|
104
|
+
const from = (valueFrom: unknown, name = "s") => [{ type: "x", settings: {}, valuesFrom: [{ targetPath: "url", valueFrom }, { targetPath: "b", valueFrom: { secretKeyRef: { name, key: "B" } } }] }];
|
|
105
|
+
expect(() => provisionedReceivers(from({ configMapKeyRef: { name: "c", key: "K" } }), "cp")).toThrow(/secretKeyRef/);
|
|
106
|
+
expect(() => provisionedReceivers(from({ secretKeyRef: { name: "s", key: "not-a-name" } }), "cp")).toThrow(/secretKeyRef/);
|
|
107
|
+
expect(() => provisionedReceivers(from({ secretKeyRef: { name: "one", key: "A" } }, "two"), "cp")).toThrow(/one secretName/);
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
describe("the policy tree", () => {
|
|
112
|
+
test("object_matchers go back to matchers in every nested route", () => {
|
|
113
|
+
const route = operatorPolicyRoute({
|
|
114
|
+
receiver: "oncall",
|
|
115
|
+
routes: [{ receiver: "tickets", matchers: ['severity="ticket"', "team=~a|b"], routes: [{ matchers: ["env!=dev"] }] }],
|
|
116
|
+
});
|
|
117
|
+
expect(provisionedRoute(route)).toEqual({
|
|
118
|
+
receiver: "oncall",
|
|
119
|
+
routes: [{ receiver: "tickets", matchers: ['severity="ticket"', 'team=~"a|b"'], routes: [{ matchers: ['env!="dev"'] }] }],
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
test("a matcher that cannot be written as a string keeps them all as objects", () => {
|
|
124
|
+
const objects = [
|
|
125
|
+
["a", "=", "x"],
|
|
126
|
+
["b", "=", 'say "hi"'],
|
|
127
|
+
];
|
|
128
|
+
expect(provisionedRoute({ receiver: "r", object_matchers: objects })).toEqual({ receiver: "r", object_matchers: objects });
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
describe("import", () => {
|
|
133
|
+
test("a rule group becomes an AlertRuleGroup, read through operatorRules, with a warning about its folder", () => {
|
|
134
|
+
const spec = {
|
|
135
|
+
instanceSelector: SELECTOR,
|
|
136
|
+
name: "errors",
|
|
137
|
+
folderRef: "grafana-folder-alerts",
|
|
138
|
+
interval: "30s",
|
|
139
|
+
rules: [
|
|
140
|
+
{
|
|
141
|
+
uid: "high-errors",
|
|
142
|
+
title: "High errors",
|
|
143
|
+
condition: "A",
|
|
144
|
+
for: "5m",
|
|
145
|
+
noDataState: "NoData",
|
|
146
|
+
execErrState: "Alerting",
|
|
147
|
+
data: [{ refId: "A", relativeTimeRange: { from: 600, to: 0 }, datasourceUid: "prom", model: { refId: "A", expr: "up", datasource: { type: "prometheus", uid: "prom" } } }],
|
|
148
|
+
},
|
|
149
|
+
],
|
|
150
|
+
};
|
|
151
|
+
const out = operatorImporter.import(site("K8s::Grafana::GrafanaAlertRuleGroup", "rules", spec));
|
|
152
|
+
expect(out.value.shape).toBe("single");
|
|
153
|
+
expect(out.value.through).toEqual({ from: "@intentius/chant-lexicon-grafana/k8s", name: "operatorRules" });
|
|
154
|
+
const [binding] = out.value.bindings;
|
|
155
|
+
expect(out.files.find((f) => f.path === binding.from)?.content).toContain(`const ${binding.name} = new AlertRuleGroup(`);
|
|
156
|
+
const text = out.files.map((f) => f.content).join("\n");
|
|
157
|
+
expect(text).toContain('folder: "grafana-folder-alerts"');
|
|
158
|
+
expect(text).toContain("new AlertRule(");
|
|
159
|
+
expect(out.warnings?.join("\n")).toContain('rule group "errors": a declaration\'s folder is a title');
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
test("a contact point that reads a Secret gets a module naming it", () => {
|
|
163
|
+
const spec = {
|
|
164
|
+
instanceSelector: SELECTOR,
|
|
165
|
+
name: "oncall",
|
|
166
|
+
receivers: [
|
|
167
|
+
{
|
|
168
|
+
uid: "oncall-slack",
|
|
169
|
+
type: "slack",
|
|
170
|
+
settings: { title: "chant" },
|
|
171
|
+
valuesFrom: [{ targetPath: "url", valueFrom: { secretKeyRef: { name: "grafana-secrets", key: "SLACK_URL" } } }],
|
|
172
|
+
},
|
|
173
|
+
],
|
|
174
|
+
};
|
|
175
|
+
const out = operatorImporter.import(site("K8s::Grafana::GrafanaContactPoint", "receivers", spec));
|
|
176
|
+
expect(out.value.through).toBeUndefined();
|
|
177
|
+
const [binding] = out.value.bindings;
|
|
178
|
+
expect(binding).toEqual({ from: "receivers.ts", name: "receivers" });
|
|
179
|
+
const module = out.files.find((f) => f.path === "receivers.ts")!.content;
|
|
180
|
+
expect(module).toContain('import { operatorReceivers } from "@intentius/chant-lexicon-grafana/k8s";');
|
|
181
|
+
expect(module).toContain('"grafana-secrets"');
|
|
182
|
+
expect(out.files.map((f) => f.content).join("\n")).toContain("${SLACK_URL}");
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
test("a contact point with no Secret is read through operatorReceivers alone", () => {
|
|
186
|
+
const spec = { name: "mail", receivers: [{ uid: "m", type: "email", settings: { addresses: "a@example.com" } }] };
|
|
187
|
+
const out = operatorImporter.import(site("K8s::Grafana::GrafanaContactPoint", "receivers", spec));
|
|
188
|
+
expect(out.value.through).toEqual({ from: "@intentius/chant-lexicon-grafana/k8s", name: "operatorReceivers" });
|
|
189
|
+
expect(out.files.some((f) => f.path === "receivers.ts")).toBe(false);
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
test("a policy, a mute timing and a template", () => {
|
|
193
|
+
const policy = operatorImporter.import(site("K8s::Grafana::GrafanaNotificationPolicy", "route", { route: { receiver: "oncall", routes: [{ object_matchers: [["severity", "=", "page"]] }] } }));
|
|
194
|
+
expect(policy.value.through?.name).toBe("operatorPolicy");
|
|
195
|
+
const text = policy.files.map((f) => f.content).join("\n");
|
|
196
|
+
expect(text).toContain("new NotificationPolicy(");
|
|
197
|
+
expect(text).toContain('severity="page"');
|
|
198
|
+
|
|
199
|
+
const mute = operatorImporter.import(site("K8s::Grafana::GrafanaMuteTiming", "time_intervals", { name: "weekends", time_intervals: [{ weekdays: ["saturday", "sunday"] }] }));
|
|
200
|
+
expect(mute.value.through?.name).toBe("operatorTimeIntervals");
|
|
201
|
+
expect(mute.files.map((f) => f.content).join("\n")).toContain("new MuteTiming(");
|
|
202
|
+
|
|
203
|
+
const template = operatorImporter.import(site("K8s::Grafana::GrafanaNotificationTemplate", "template", { name: "t", template: '{{ define "t" }}x{{ end }}' }));
|
|
204
|
+
expect(template.value.through?.name).toBe("operatorTemplate");
|
|
205
|
+
expect(template.files.map((f) => f.content).join("\n")).toContain("new NotificationTemplate(");
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
test("a policy with no receiver, and a group with no folder, throw so the host keeps them as written", () => {
|
|
209
|
+
expect(() => operatorImporter.import(site("K8s::Grafana::GrafanaNotificationPolicy", "route", { route: { group_by: ["a"] } }))).toThrow();
|
|
210
|
+
expect(() => operatorImporter.import(site("K8s::Grafana::GrafanaAlertRuleGroup", "rules", { name: "g", rules: [] }))).toThrow(/names no folder/);
|
|
211
|
+
});
|
|
212
|
+
});
|
|
213
|
+
});
|