@intentius/chant-lexicon-grafana 0.97.0 → 0.99.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +15 -5
- package/dist/actions/grafana.d.ts +24 -0
- package/dist/actions/grafana.d.ts.map +1 -0
- package/dist/actions/index.d.ts +2 -0
- package/dist/actions/index.d.ts.map +1 -0
- package/dist/alerting-build.d.ts +128 -0
- package/dist/alerting-build.d.ts.map +1 -0
- package/dist/alerting.d.ts +273 -0
- package/dist/alerting.d.ts.map +1 -0
- package/dist/api/apply.d.ts +156 -0
- package/dist/api/apply.d.ts.map +1 -0
- package/dist/api/bind.d.ts +32 -0
- package/dist/api/bind.d.ts.map +1 -0
- package/dist/api/client.d.ts +76 -0
- package/dist/api/client.d.ts.map +1 -0
- package/dist/api/converge.d.ts +33 -0
- package/dist/api/converge.d.ts.map +1 -0
- package/dist/api/dashboards.d.ts +102 -0
- package/dist/api/dashboards.d.ts.map +1 -0
- package/dist/api/datasources.d.ts +26 -0
- package/dist/api/datasources.d.ts.map +1 -0
- package/dist/api/fake-grafana-writes.d.ts +41 -0
- package/dist/api/fake-grafana-writes.d.ts.map +1 -0
- package/dist/api/fake-grafana.d.ts +61 -0
- package/dist/api/fake-grafana.d.ts.map +1 -0
- package/dist/api/folders.d.ts +110 -0
- package/dist/api/folders.d.ts.map +1 -0
- package/dist/api/library-panels.d.ts +52 -0
- package/dist/api/library-panels.d.ts.map +1 -0
- package/dist/build.d.ts +30 -2
- package/dist/build.d.ts.map +1 -1
- package/dist/catalog.d.ts +1 -1
- package/dist/catalog.d.ts.map +1 -1
- package/dist/codegen/docs.d.ts.map +1 -1
- package/dist/codegen/schema-types.d.ts +3 -3
- package/dist/codegen/schema-types.d.ts.map +1 -1
- package/dist/composites/agent-dashboard.d.ts.map +1 -1
- package/dist/composites/catalog.d.ts.map +1 -1
- package/dist/composites/index.d.ts +4 -2
- package/dist/composites/index.d.ts.map +1 -1
- package/dist/composites/red-dashboard.d.ts +21 -2
- package/dist/composites/red-dashboard.d.ts.map +1 -1
- package/dist/composites/shared.d.ts +7 -0
- package/dist/composites/shared.d.ts.map +1 -1
- package/dist/composites/slo-alert-rules.d.ts +92 -0
- package/dist/composites/slo-alert-rules.d.ts.map +1 -0
- package/dist/config.d.ts +111 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/contact-point-secrets.d.ts +17 -0
- package/dist/contact-point-secrets.d.ts.map +1 -0
- package/dist/dashboard.d.ts +16 -2
- package/dist/dashboard.d.ts.map +1 -1
- package/dist/datasource-refs.d.ts +95 -0
- package/dist/datasource-refs.d.ts.map +1 -0
- package/dist/datasource.d.ts +29 -0
- package/dist/datasource.d.ts.map +1 -1
- package/dist/deep-observe-hooks.d.ts +64 -0
- package/dist/deep-observe-hooks.d.ts.map +1 -0
- package/dist/deep-observe.d.ts +42 -0
- package/dist/deep-observe.d.ts.map +1 -0
- package/dist/describe-resources.d.ts +49 -0
- package/dist/describe-resources.d.ts.map +1 -0
- package/dist/detect.d.ts +28 -1
- package/dist/detect.d.ts.map +1 -1
- package/dist/export-resources.d.ts +38 -0
- package/dist/export-resources.d.ts.map +1 -0
- package/dist/import/alerting.d.ts +40 -0
- package/dist/import/alerting.d.ts.map +1 -0
- package/dist/import/edits.d.ts +47 -0
- package/dist/import/edits.d.ts.map +1 -0
- package/dist/import/embedded.d.ts +14 -0
- package/dist/import/embedded.d.ts.map +1 -0
- package/dist/import/generator.d.ts +60 -0
- package/dist/import/generator.d.ts.map +1 -0
- package/dist/import/live-export.d.ts +108 -0
- package/dist/import/live-export.d.ts.map +1 -0
- package/dist/import/mappings.d.ts +83 -0
- package/dist/import/mappings.d.ts.map +1 -0
- package/dist/import/model.d.ts +98 -0
- package/dist/import/model.d.ts.map +1 -0
- package/dist/import/normalize-alerting.d.ts +26 -0
- package/dist/import/normalize-alerting.d.ts.map +1 -0
- package/dist/import/normalize.d.ts +74 -0
- package/dist/import/normalize.d.ts.map +1 -0
- package/dist/import/parser.d.ts +110 -0
- package/dist/import/parser.d.ts.map +1 -0
- package/dist/import/report.d.ts +26 -0
- package/dist/import/report.d.ts.map +1 -0
- package/dist/import/testdata/fixtures.d.ts +35 -0
- package/dist/import/testdata/fixtures.d.ts.map +1 -0
- package/dist/import/v2.d.ts +72 -0
- package/dist/import/v2.d.ts.map +1 -0
- package/dist/index.d.ts +26 -9
- package/dist/index.d.ts.map +1 -1
- package/dist/integrity.json +18 -10
- package/dist/k8s.d.ts +107 -0
- package/dist/k8s.d.ts.map +1 -0
- package/dist/lint/audit-catalog.d.ts +2 -1
- package/dist/lint/audit-catalog.d.ts.map +1 -1
- package/dist/lint/post-synth/graf101.d.ts +2 -2
- package/dist/lint/post-synth/graf102.d.ts +1 -1
- package/dist/lint/post-synth/graf107.d.ts +1 -1
- package/dist/lint/post-synth/graf108.d.ts +8 -0
- package/dist/lint/post-synth/graf108.d.ts.map +1 -0
- package/dist/lint/post-synth/graf109.d.ts +8 -0
- package/dist/lint/post-synth/graf109.d.ts.map +1 -0
- package/dist/lint/post-synth/graf111.d.ts +8 -0
- package/dist/lint/post-synth/graf111.d.ts.map +1 -0
- package/dist/lint/post-synth/graf112.d.ts +8 -0
- package/dist/lint/post-synth/graf112.d.ts.map +1 -0
- package/dist/lint/post-synth/graf113.d.ts +8 -0
- package/dist/lint/post-synth/graf113.d.ts.map +1 -0
- package/dist/lint/post-synth/graf114.d.ts +8 -0
- package/dist/lint/post-synth/graf114.d.ts.map +1 -0
- package/dist/lint/post-synth/graf115.d.ts +8 -0
- package/dist/lint/post-synth/graf115.d.ts.map +1 -0
- package/dist/lint/post-synth/grafana-helpers.d.ts +7 -5
- package/dist/lint/post-synth/grafana-helpers.d.ts.map +1 -1
- package/dist/lint/post-synth/index.d.ts.map +1 -1
- package/dist/lint/rules/literal-secret.d.ts +7 -3
- package/dist/lint/rules/literal-secret.d.ts.map +1 -1
- package/dist/lsp/hover.d.ts.map +1 -1
- package/dist/manifest.json +1 -1
- package/dist/members.d.ts +27 -0
- package/dist/members.d.ts.map +1 -0
- package/dist/meta.json +135 -0
- package/dist/okf/index.md +36 -2
- package/dist/okf/rules/GRAF002.md +3 -2
- package/dist/okf/rules/GRAF101.md +3 -2
- package/dist/okf/rules/GRAF102.md +1 -0
- package/dist/okf/rules/GRAF108.md +11 -0
- package/dist/okf/rules/GRAF109.md +15 -0
- package/dist/okf/rules/GRAF111.md +11 -0
- package/dist/okf/rules/GRAF112.md +16 -0
- package/dist/okf/rules/GRAF113.md +16 -0
- package/dist/okf/rules/GRAF114.md +11 -0
- package/dist/okf/rules/GRAF115.md +11 -0
- package/dist/okf/types/AlertListPanel.md +9 -0
- package/dist/okf/types/AlertQuery.md +9 -0
- package/dist/okf/types/AlertRule.md +9 -0
- package/dist/okf/types/AlertRuleGroup.md +9 -0
- package/dist/okf/types/BarChartPanel.md +9 -0
- package/dist/okf/types/BarGaugePanel.md +9 -0
- package/dist/okf/types/CanvasPanel.md +9 -0
- package/dist/okf/types/ClassicConditionsExpression.md +9 -0
- package/dist/okf/types/ContactPoint.md +14 -0
- package/dist/okf/types/Dashboard.md +1 -0
- package/dist/okf/types/Datasource.md +3 -2
- package/dist/okf/types/ExternalDatasource.md +15 -0
- package/dist/okf/types/FlameGraphPanel.md +9 -0
- package/dist/okf/types/GeomapPanel.md +9 -0
- package/dist/okf/types/HistogramPanel.md +9 -0
- package/dist/okf/types/MathExpression.md +9 -0
- package/dist/okf/types/MuteTiming.md +13 -0
- package/dist/okf/types/NodeGraphPanel.md +9 -0
- package/dist/okf/types/NotificationPolicy.md +9 -0
- package/dist/okf/types/NotificationTemplate.md +9 -0
- package/dist/okf/types/PieChartPanel.md +9 -0
- package/dist/okf/types/ReduceExpression.md +9 -0
- package/dist/okf/types/ResampleExpression.md +9 -0
- package/dist/okf/types/SqlExpression.md +9 -0
- package/dist/okf/types/StateTimelinePanel.md +9 -0
- package/dist/okf/types/StatusHistoryPanel.md +9 -0
- package/dist/okf/types/ThresholdExpression.md +9 -0
- package/dist/okf/types/TrendPanel.md +9 -0
- package/dist/okf/types/XYChartPanel.md +9 -0
- package/dist/op/activities/grafana-apply.d.ts +75 -0
- package/dist/op/activities/grafana-apply.d.ts.map +1 -0
- package/dist/op/activities/index.d.ts +9 -0
- package/dist/op/activities/index.d.ts.map +1 -0
- package/dist/op/builders.d.ts +24 -0
- package/dist/op/builders.d.ts.map +1 -0
- package/dist/ownership.d.ts +67 -0
- package/dist/ownership.d.ts.map +1 -0
- package/dist/panel-options.d.ts +65 -0
- package/dist/panel-options.d.ts.map +1 -0
- package/dist/panels.d.ts +46 -9
- package/dist/panels.d.ts.map +1 -1
- package/dist/pin.d.ts +29 -7
- package/dist/pin.d.ts.map +1 -1
- package/dist/plugin.d.ts.map +1 -1
- package/dist/promql-check.d.ts +51 -0
- package/dist/promql-check.d.ts.map +1 -0
- package/dist/query.d.ts +9 -8
- package/dist/query.d.ts.map +1 -1
- package/dist/rules/graf101.ts +3 -3
- package/dist/rules/graf102.ts +1 -1
- package/dist/rules/graf107.ts +1 -1
- package/dist/rules/graf108.ts +17 -0
- package/dist/rules/graf109.ts +17 -0
- package/dist/rules/graf111.ts +17 -0
- package/dist/rules/graf112.ts +17 -0
- package/dist/rules/graf113.ts +17 -0
- package/dist/rules/graf114.ts +17 -0
- package/dist/rules/graf115.ts +17 -0
- package/dist/rules/grafana-helpers.ts +15 -8
- package/dist/rules/literal-secret.ts +60 -6
- package/dist/schema/barchart.gen.d.ts +198 -0
- package/dist/schema/barchart.gen.d.ts.map +1 -0
- package/dist/schema/bargauge.gen.d.ts +97 -0
- package/dist/schema/bargauge.gen.d.ts.map +1 -0
- package/dist/schema/canvas.gen.d.ts +152 -0
- package/dist/schema/canvas.gen.d.ts.map +1 -0
- package/dist/schema/dashboard.gen.d.ts +196 -21
- package/dist/schema/dashboard.gen.d.ts.map +1 -1
- package/dist/schema/expr.gen.d.ts +606 -0
- package/dist/schema/expr.gen.d.ts.map +1 -0
- package/dist/schema/gauge.gen.d.ts.map +1 -1
- package/dist/schema/geomap.gen.d.ts +100 -0
- package/dist/schema/geomap.gen.d.ts.map +1 -0
- package/dist/schema/heatmap.gen.d.ts.map +1 -1
- package/dist/schema/histogram.gen.d.ts +132 -0
- package/dist/schema/histogram.gen.d.ts.map +1 -0
- package/dist/schema/index.d.ts +12 -0
- package/dist/schema/index.d.ts.map +1 -1
- package/dist/schema/logs.gen.d.ts +1 -0
- package/dist/schema/logs.gen.d.ts.map +1 -1
- package/dist/schema/loki.gen.d.ts.map +1 -1
- package/dist/schema/nodegraph.gen.d.ts +49 -0
- package/dist/schema/nodegraph.gen.d.ts.map +1 -0
- package/dist/schema/piechart.gen.d.ts +127 -0
- package/dist/schema/piechart.gen.d.ts.map +1 -0
- package/dist/schema/prometheus.gen.d.ts.map +1 -1
- package/dist/schema/stat.gen.d.ts.map +1 -1
- package/dist/schema/statetimeline.gen.d.ts +124 -0
- package/dist/schema/statetimeline.gen.d.ts.map +1 -0
- package/dist/schema/statushistory.gen.d.ts +114 -0
- package/dist/schema/statushistory.gen.d.ts.map +1 -0
- package/dist/schema/table.gen.d.ts +22 -3
- package/dist/schema/table.gen.d.ts.map +1 -1
- package/dist/schema/tempo.gen.d.ts.map +1 -1
- package/dist/schema/text.gen.d.ts.map +1 -1
- package/dist/schema/timeseries.gen.d.ts +5 -1
- package/dist/schema/timeseries.gen.d.ts.map +1 -1
- package/dist/schema/trend.gen.d.ts +180 -0
- package/dist/schema/trend.gen.d.ts.map +1 -0
- package/dist/schema/xychart.gen.d.ts +145 -0
- package/dist/schema/xychart.gen.d.ts.map +1 -0
- package/dist/schema-validate.d.ts +30 -4
- package/dist/schema-validate.d.ts.map +1 -1
- package/dist/serializer.d.ts +11 -3
- package/dist/serializer.d.ts.map +1 -1
- package/dist/skill-defs.d.ts.map +1 -1
- package/dist/skills/chant-grafana-alerting.md +49 -0
- package/dist/skills/chant-grafana-provisioning.md +3 -3
- package/dist/skills/chant-grafana.md +12 -6
- package/dist/spec/fetch-units-cli.d.ts +3 -0
- package/dist/spec/fetch-units-cli.d.ts.map +1 -0
- package/dist/spec/overlay.d.ts +43 -0
- package/dist/spec/overlay.d.ts.map +1 -0
- package/dist/spec/schemas.d.ts +13 -7
- package/dist/spec/schemas.d.ts.map +1 -1
- package/dist/spec/units.d.ts +35 -0
- package/dist/spec/units.d.ts.map +1 -0
- package/dist/spec/units.gen.d.ts +2 -0
- package/dist/spec/units.gen.d.ts.map +1 -0
- package/dist/transformations.d.ts +459 -0
- package/dist/transformations.d.ts.map +1 -0
- package/dist/util.d.ts +6 -0
- package/dist/util.d.ts.map +1 -1
- package/dist/validate-alerting.d.ts +65 -0
- package/dist/validate-alerting.d.ts.map +1 -0
- package/dist/validate-output.d.ts +49 -9
- package/dist/validate-output.d.ts.map +1 -1
- package/dist/validate.d.ts.map +1 -1
- package/dist/variables.d.ts +7 -2
- package/dist/variables.d.ts.map +1 -1
- package/package.json +12 -4
- package/src/actions/actions.test.ts +24 -0
- package/src/actions/grafana.ts +40 -0
- package/src/actions/index.ts +1 -0
- package/src/alerting-build.ts +376 -0
- package/src/alerting.e2e.test.ts +212 -0
- package/src/alerting.test.ts +272 -0
- package/src/alerting.ts +435 -0
- package/src/api/apply.test.ts +238 -0
- package/src/api/apply.ts +384 -0
- package/src/api/bind.ts +58 -0
- package/src/api/client.ts +148 -0
- package/src/api/converge.ts +63 -0
- package/src/api/dashboards.ts +247 -0
- package/src/api/datasources.ts +33 -0
- package/src/api/fake-grafana-writes.ts +201 -0
- package/src/api/fake-grafana.ts +186 -0
- package/src/api/folders.ts +265 -0
- package/src/api/library-panels.ts +101 -0
- package/src/build.test.ts +210 -5
- package/src/build.ts +180 -43
- package/src/catalog.ts +86 -2
- package/src/codegen/docs.ts +42 -9
- package/src/codegen/schema-types.ts +16 -6
- package/src/composites/agent-dashboard.ts +6 -3
- package/src/composites/catalog.test.ts +2 -2
- package/src/composites/catalog.ts +82 -0
- package/src/composites/composites.test.ts +73 -9
- package/src/composites/index.ts +4 -2
- package/src/composites/queries.e2e.test.ts +374 -0
- package/src/composites/red-dashboard.ts +83 -12
- package/src/composites/shared.ts +11 -0
- package/src/composites/slo-alert-rules.ts +149 -0
- package/src/config.test.ts +61 -0
- package/src/config.ts +138 -0
- package/src/contact-point-secrets.ts +63 -0
- package/src/dashboard.ts +16 -2
- package/src/datasource-refs.test.ts +56 -0
- package/src/datasource-refs.ts +189 -0
- package/src/datasource.ts +52 -0
- package/src/deep-observe-hooks.ts +252 -0
- package/src/deep-observe.test.ts +265 -0
- package/src/deep-observe.ts +164 -0
- package/src/describe-resources.test.ts +255 -0
- package/src/describe-resources.ts +209 -0
- package/src/detect.ts +79 -2
- package/src/export-resources.test.ts +129 -0
- package/src/export-resources.ts +84 -0
- package/src/generated/lexicon-grafana.json +135 -0
- package/src/import/alerting-roundtrip.test.ts +161 -0
- package/src/import/alerting.ts +500 -0
- package/src/import/edits.ts +95 -0
- package/src/import/embedded.test.ts +45 -0
- package/src/import/embedded.ts +47 -0
- package/src/import/generated-types.e2e.test.ts +118 -0
- package/src/import/generator.test.ts +210 -0
- package/src/import/generator.ts +462 -0
- package/src/import/live-export.ts +264 -0
- package/src/import/mappings.ts +264 -0
- package/src/import/model.ts +110 -0
- package/src/import/normalize-alerting.ts +101 -0
- package/src/import/normalize.test.ts +117 -0
- package/src/import/normalize.ts +364 -0
- package/src/import/parser.test.ts +440 -0
- package/src/import/parser.ts +1236 -0
- package/src/import/report.ts +62 -0
- package/src/import/roundtrip.test.ts +468 -0
- package/src/import/testdata/fixtures.ts +129 -0
- package/src/import/v2.test.ts +292 -0
- package/src/import/v2.ts +803 -0
- package/src/import.e2e.test.ts +31 -84
- package/src/import.test.ts +9 -3
- package/src/index.ts +190 -5
- package/src/k8s.test.ts +98 -0
- package/src/k8s.ts +197 -0
- package/src/lint/audit-catalog.ts +60 -3
- package/src/lint/core-lint.test.ts +43 -0
- package/src/lint/post-synth/graf101.ts +3 -3
- package/src/lint/post-synth/graf102.ts +1 -1
- package/src/lint/post-synth/graf107.ts +1 -1
- package/src/lint/post-synth/graf108.ts +17 -0
- package/src/lint/post-synth/graf109.ts +17 -0
- package/src/lint/post-synth/graf111.ts +17 -0
- package/src/lint/post-synth/graf112.ts +17 -0
- package/src/lint/post-synth/graf113.ts +17 -0
- package/src/lint/post-synth/graf114.ts +17 -0
- package/src/lint/post-synth/graf115.ts +17 -0
- package/src/lint/post-synth/grafana-helpers.ts +15 -8
- package/src/lint/post-synth/index.ts +14 -0
- package/src/lint/post-synth/post-synth.test.ts +235 -11
- package/src/lint/rules/literal-secret.ts +60 -6
- package/src/lint/rules/rules.test.ts +27 -0
- package/src/lsp/completions.test.ts +5 -0
- package/src/lsp/hover.test.ts +7 -0
- package/src/lsp/hover.ts +8 -1
- package/src/members.ts +68 -0
- package/src/observe.e2e.test.ts +255 -0
- package/src/op/activities/grafana-apply.e2e.test.ts +175 -0
- package/src/op/activities/grafana-apply.test.ts +229 -0
- package/src/op/activities/grafana-apply.ts +171 -0
- package/src/op/activities/index.ts +8 -0
- package/src/op/builders.test.ts +40 -0
- package/src/op/builders.ts +34 -0
- package/src/ownership.ts +103 -0
- package/src/panel-options.ts +66 -0
- package/src/panels.test.ts +51 -0
- package/src/panels.ts +148 -10
- package/src/pin.ts +57 -6
- package/src/plugin.test.ts +5 -2
- package/src/plugin.ts +66 -0
- package/src/promql-check.test.ts +108 -0
- package/src/promql-check.ts +189 -0
- package/src/query.ts +13 -8
- package/src/schema/barchart.gen.ts +221 -0
- package/src/schema/bargauge.gen.ts +111 -0
- package/src/schema/canvas.gen.ts +176 -0
- package/src/schema/dashboard.gen.ts +206 -22
- package/src/schema/expr.gen.ts +618 -0
- package/src/schema/gauge.gen.ts +2 -1
- package/src/schema/geomap.gen.ts +112 -0
- package/src/schema/heatmap.gen.ts +2 -1
- package/src/schema/histogram.gen.ts +151 -0
- package/src/schema/index.ts +12 -0
- package/src/schema/logs.gen.ts +3 -1
- package/src/schema/loki.gen.ts +2 -1
- package/src/schema/nodegraph.gen.ts +58 -0
- package/src/schema/piechart.gen.ts +146 -0
- package/src/schema/prometheus.gen.ts +2 -1
- package/src/schema/stat.gen.ts +2 -1
- package/src/schema/statetimeline.gen.ts +143 -0
- package/src/schema/statushistory.gen.ts +132 -0
- package/src/schema/table.gen.ts +27 -4
- package/src/schema/tempo.gen.ts +2 -1
- package/src/schema/text.gen.ts +2 -1
- package/src/schema/timeseries.gen.ts +7 -2
- package/src/schema/trend.gen.ts +208 -0
- package/src/schema/xychart.gen.ts +167 -0
- package/src/schema-overlay.test.ts +282 -0
- package/src/schema-validate.ts +141 -50
- package/src/schema.test.ts +4 -2
- package/src/serializer.test.ts +13 -1
- package/src/serializer.ts +11 -3
- package/src/skill-defs.ts +17 -0
- package/src/skills/chant-grafana-alerting.md +49 -0
- package/src/skills/chant-grafana-provisioning.md +3 -3
- package/src/skills/chant-grafana.md +12 -6
- package/src/spec/fetch-cli.ts +3 -3
- package/src/spec/fetch-units-cli.ts +37 -0
- package/src/spec/overlay/canvas.overlay.json +142 -0
- package/src/spec/overlay/dashboard.overlay.json +378 -0
- package/src/spec/overlay/expr.overlay.json +14 -0
- package/src/spec/overlay/geomap.overlay.json +35 -0
- package/src/spec/overlay/logs.overlay.json +14 -0
- package/src/spec/overlay/nodegraph.overlay.json +31 -0
- package/src/spec/overlay/piechart.overlay.json +17 -0
- package/src/spec/overlay/table.overlay.json +84 -0
- package/src/spec/overlay/timeseries.overlay.json +21 -0
- package/src/spec/overlay/trend.overlay.json +17 -0
- package/src/spec/overlay/xychart.overlay.json +16 -0
- package/src/spec/overlay.ts +105 -0
- package/src/spec/schemas/barchart.jsonschema.json +419 -0
- package/src/spec/schemas/bargauge.jsonschema.json +224 -0
- package/src/spec/schemas/canvas.jsonschema.json +356 -0
- package/src/spec/schemas/dashboardv2.jsonschema.json +3387 -0
- package/src/spec/schemas/expr.jsonschema.json +987 -0
- package/src/spec/schemas/geomap.jsonschema.json +220 -0
- package/src/spec/schemas/histogram.jsonschema.json +308 -0
- package/src/spec/schemas/nodegraph.jsonschema.json +77 -0
- package/src/spec/schemas/piechart.jsonschema.json +275 -0
- package/src/spec/schemas/statetimeline.jsonschema.json +324 -0
- package/src/spec/schemas/statushistory.jsonschema.json +292 -0
- package/src/spec/schemas/trend.jsonschema.json +449 -0
- package/src/spec/schemas/xychart.jsonschema.json +428 -0
- package/src/spec/schemas.ts +22 -10
- package/src/spec/units.gen.ts +281 -0
- package/src/spec/units.ts +77 -0
- package/src/transformations.test.ts +65 -0
- package/src/transformations.ts +574 -0
- package/src/util.ts +8 -0
- package/src/validate-alerting.test.ts +255 -0
- package/src/validate-alerting.ts +428 -0
- package/src/validate-output.ts +332 -101
- package/src/validate.ts +14 -0
- package/src/variables.ts +7 -2
package/src/k8s.ts
ADDED
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Grafana dashboards and provisioning delivered to Kubernetes as ConfigMaps
|
|
3
|
+
* (#2954), from `@intentius/chant-lexicon-grafana/k8s`.
|
|
4
|
+
*
|
|
5
|
+
* `GrafanaConfigMaps` writes one ConfigMap per dashboard and one for the
|
|
6
|
+
* datasource provisioning file, labelled the way the Grafana Helm chart's
|
|
7
|
+
* sidecar (kiwigrid/k8s-sidecar, also in kube-prometheus-stack) finds them:
|
|
8
|
+
* `grafana_dashboard: "1"` and `grafana_datasource: "1"`. A dashboard's
|
|
9
|
+
* folder goes in the `k8s-sidecar-target-directory` annotation the sidecar
|
|
10
|
+
* reads by default. Each dashboard ConfigMap holds `<uid>.json`, the same
|
|
11
|
+
* text as `dashboardJson(dashboard)`, which is also what the k8s importer
|
|
12
|
+
* turns such a ConfigMap back into (#2962).
|
|
13
|
+
*
|
|
14
|
+
* A Grafana run without the sidecar (a plain Deployment, as in
|
|
15
|
+
* `examples/agent-observability`) mounts the same ConfigMaps with
|
|
16
|
+
* `grafanaVolumes()`, which also needs the dashboard provider file that
|
|
17
|
+
* `GrafanaConfigMaps` writes to a third ConfigMap. The provisioning files go
|
|
18
|
+
* under `/etc/grafana/provisioning` and each dashboard folder is a volume of
|
|
19
|
+
* its own under the provider's path: inside a ConfigMap volume a
|
|
20
|
+
* subdirectory is a symlink, and Grafana's dashboard provider does not
|
|
21
|
+
* follow symlinked directories.
|
|
22
|
+
*
|
|
23
|
+
* This module is the only part of the grafana lexicon that loads the k8s
|
|
24
|
+
* lexicon; nothing else imports it.
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
import { Composite, type CompositeInstance } from "@intentius/chant/composite";
|
|
28
|
+
import type { Declarable } from "@intentius/chant/declarable";
|
|
29
|
+
import { ConfigMap } from "@intentius/chant-lexicon-k8s/generated/index";
|
|
30
|
+
import { buildGrafana, DASHBOARD_PROVIDERS_FILE, DASHBOARDS_DIR, DATASOURCES_FILE } from "./build";
|
|
31
|
+
import { DEFAULT_DASHBOARDS_PATH } from "./dashboard";
|
|
32
|
+
|
|
33
|
+
/** The label the Grafana Helm chart's sidecar looks for on dashboard ConfigMaps (`sidecar.dashboards.label`). */
|
|
34
|
+
export const SIDECAR_DASHBOARD_LABEL = "grafana_dashboard";
|
|
35
|
+
/** The label it looks for on datasource ConfigMaps (`sidecar.datasources.label`). */
|
|
36
|
+
export const SIDECAR_DATASOURCE_LABEL = "grafana_datasource";
|
|
37
|
+
/** The annotation the sidecar reads a dashboard's target directory from, by default (its `FOLDER_ANNOTATION`). */
|
|
38
|
+
export const SIDECAR_FOLDER_ANNOTATION = "k8s-sidecar-target-directory";
|
|
39
|
+
|
|
40
|
+
/** Where Grafana reads provisioning files. */
|
|
41
|
+
export const GRAFANA_PROVISIONING_PATH = "/etc/grafana/provisioning";
|
|
42
|
+
|
|
43
|
+
export interface GrafanaConfigMapsProps {
|
|
44
|
+
/** The grafana declarations to deliver: dashboards, datasources, `DashboardProvider`s. Anything else is ignored. */
|
|
45
|
+
entities: Iterable<Declarable>;
|
|
46
|
+
namespace?: string;
|
|
47
|
+
/** Prefix of every ConfigMap name. Defaults to `grafana`. */
|
|
48
|
+
name?: string;
|
|
49
|
+
/** Labels added to every ConfigMap, beside the sidecar's. */
|
|
50
|
+
labels?: Record<string, string>;
|
|
51
|
+
/** The dashboard label as `[key, value]`, or `false` for none. Defaults to `["grafana_dashboard", "1"]`. */
|
|
52
|
+
dashboardLabel?: readonly [string, string] | false;
|
|
53
|
+
/** The datasource label as `[key, value]`, or `false` for none. Defaults to `["grafana_datasource", "1"]`. */
|
|
54
|
+
datasourceLabel?: readonly [string, string] | false;
|
|
55
|
+
/** The annotation holding a dashboard's folder, or `false` for none. Defaults to `k8s-sidecar-target-directory`. */
|
|
56
|
+
folderAnnotation?: string | false;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** The `metadata.name` of each ConfigMap, and which file it holds. */
|
|
60
|
+
export interface GrafanaConfigMapLayout {
|
|
61
|
+
dashboards: Array<{ configMap: string; key: string; folder: string; uid: string; text: string }>;
|
|
62
|
+
datasources?: { configMap: string; key: string; text: string };
|
|
63
|
+
providers?: { configMap: string; key: string; text: string };
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** A DNS-1123 subdomain made from a name: lower case, `[a-z0-9-.]`, at most 253 characters. */
|
|
67
|
+
function dnsName(text: string): string {
|
|
68
|
+
const out = text
|
|
69
|
+
.toLowerCase()
|
|
70
|
+
.replace(/[^a-z0-9.-]+/g, "-")
|
|
71
|
+
.replace(/^[^a-z0-9]+|[^a-z0-9]+$/g, "");
|
|
72
|
+
return (out || "x").slice(0, 253).replace(/[^a-z0-9]+$/, "");
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function basename(path: string): string {
|
|
76
|
+
return path.slice(path.lastIndexOf("/") + 1);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** Which ConfigMap each file goes in, and under which key. `GrafanaConfigMaps` and `grafanaVolumes` both read it. */
|
|
80
|
+
export function grafanaConfigMapLayout(props: Pick<GrafanaConfigMapsProps, "entities" | "name">): GrafanaConfigMapLayout {
|
|
81
|
+
const prefix = props.name ?? "grafana";
|
|
82
|
+
const built = buildGrafana(props.entities);
|
|
83
|
+
const taken = new Set<string>();
|
|
84
|
+
const unique = (base: string) => {
|
|
85
|
+
let out = base;
|
|
86
|
+
for (let n = 2; taken.has(out); n++) out = `${base}-${n}`;
|
|
87
|
+
taken.add(out);
|
|
88
|
+
return out;
|
|
89
|
+
};
|
|
90
|
+
const layout: GrafanaConfigMapLayout = {
|
|
91
|
+
dashboards: built.dashboards.map((d) => {
|
|
92
|
+
const dir = d.file.slice(DASHBOARDS_DIR.length + 1, d.file.lastIndexOf("/") + 1).replace(/\/$/, "");
|
|
93
|
+
return { configMap: unique(dnsName(`${prefix}-dashboard-${d.uid}`)), key: basename(d.file), folder: dir, uid: d.uid, text: built.files[d.file] };
|
|
94
|
+
}),
|
|
95
|
+
};
|
|
96
|
+
if (built.files[DATASOURCES_FILE] !== undefined) {
|
|
97
|
+
layout.datasources = { configMap: unique(dnsName(`${prefix}-datasources`)), key: basename(DATASOURCES_FILE), text: built.files[DATASOURCES_FILE] };
|
|
98
|
+
}
|
|
99
|
+
if (built.files[DASHBOARD_PROVIDERS_FILE] !== undefined) {
|
|
100
|
+
layout.providers = { configMap: unique(dnsName(`${prefix}-dashboard-providers`)), key: basename(DASHBOARD_PROVIDERS_FILE), text: built.files[DASHBOARD_PROVIDERS_FILE] };
|
|
101
|
+
}
|
|
102
|
+
return layout;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
106
|
+
type ConfigMapEntity = InstanceType<typeof ConfigMap> & Declarable;
|
|
107
|
+
|
|
108
|
+
export type GrafanaConfigMapsMembers = Record<string, ConfigMapEntity>;
|
|
109
|
+
export type GrafanaConfigMapsInstance = CompositeInstance<GrafanaConfigMapsMembers> & GrafanaConfigMapsMembers;
|
|
110
|
+
|
|
111
|
+
/** A member name from a ConfigMap name: `grafana-dashboard-red` becomes `dashboardRed`. */
|
|
112
|
+
function memberName(configMap: string, prefix: string): string {
|
|
113
|
+
const rest = configMap.startsWith(`${prefix}-`) ? configMap.slice(prefix.length + 1) : configMap;
|
|
114
|
+
const words = rest.split(/[^A-Za-z0-9]+/).filter(Boolean);
|
|
115
|
+
return words.map((w, i) => (i === 0 ? w : w.charAt(0).toUpperCase() + w.slice(1))).join("") || "configMap";
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* One ConfigMap per dashboard, one for the datasources and one for the
|
|
120
|
+
* dashboard provider, labelled for the Grafana sidecar. Export the result
|
|
121
|
+
* from a k8s build root; the members are named after the ConfigMaps
|
|
122
|
+
* (`dashboardGenaiAgents`, `datasources`, `dashboardProviders`).
|
|
123
|
+
*/
|
|
124
|
+
export const GrafanaConfigMaps = Composite<GrafanaConfigMapsProps, GrafanaConfigMapsMembers>((props) => {
|
|
125
|
+
const prefix = dnsName(props.name ?? "grafana");
|
|
126
|
+
const layout = grafanaConfigMapLayout({ ...props, name: prefix });
|
|
127
|
+
const dashboardLabel = props.dashboardLabel === undefined ? ([SIDECAR_DASHBOARD_LABEL, "1"] as const) : props.dashboardLabel;
|
|
128
|
+
const datasourceLabel = props.datasourceLabel === undefined ? ([SIDECAR_DATASOURCE_LABEL, "1"] as const) : props.datasourceLabel;
|
|
129
|
+
const folderAnnotation = props.folderAnnotation === undefined ? SIDECAR_FOLDER_ANNOTATION : props.folderAnnotation;
|
|
130
|
+
|
|
131
|
+
const metadata = (name: string, label: readonly [string, string] | false, annotations?: Record<string, string>) => ({
|
|
132
|
+
name,
|
|
133
|
+
...(props.namespace ? { namespace: props.namespace } : {}),
|
|
134
|
+
labels: { ...(props.labels ?? {}), ...(label ? { [label[0]]: label[1] } : {}) },
|
|
135
|
+
...(annotations ? { annotations } : {}),
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
const members: GrafanaConfigMapsMembers = {};
|
|
139
|
+
for (const d of layout.dashboards) {
|
|
140
|
+
const annotations = folderAnnotation && d.folder ? { [folderAnnotation]: d.folder } : undefined;
|
|
141
|
+
members[memberName(d.configMap, prefix)] = new ConfigMap({ metadata: metadata(d.configMap, dashboardLabel, annotations), data: { [d.key]: d.text } }) as ConfigMapEntity;
|
|
142
|
+
}
|
|
143
|
+
for (const f of [layout.datasources, layout.providers]) {
|
|
144
|
+
if (!f) continue;
|
|
145
|
+
const label = f === layout.datasources ? datasourceLabel : false;
|
|
146
|
+
members[memberName(f.configMap, prefix)] = new ConfigMap({ metadata: metadata(f.configMap, label), data: { [f.key]: f.text } }) as ConfigMapEntity;
|
|
147
|
+
}
|
|
148
|
+
return members;
|
|
149
|
+
}, "GrafanaConfigMaps");
|
|
150
|
+
|
|
151
|
+
export interface GrafanaVolumesOptions {
|
|
152
|
+
/** Where the dashboard provider reads dashboards. Defaults to `/var/lib/grafana/dashboards`, the provider chant writes by default. */
|
|
153
|
+
dashboardsPath?: string;
|
|
154
|
+
/** Prefix of the volume names. Defaults to `grafana`. */
|
|
155
|
+
volumeName?: string;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export interface GrafanaVolumes {
|
|
159
|
+
volumes: Array<Record<string, unknown>>;
|
|
160
|
+
volumeMounts: Array<{ name: string; mountPath: string; readOnly: true }>;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* The pod volumes and container mounts that put `GrafanaConfigMaps`'s files
|
|
165
|
+
* where a Grafana without the sidecar reads them: the provisioning files
|
|
166
|
+
* under `/etc/grafana/provisioning`, and each dashboard folder under the
|
|
167
|
+
* provider's path, one projected volume per folder. Pass the same `entities`
|
|
168
|
+
* and `name` as to `GrafanaConfigMaps`.
|
|
169
|
+
*/
|
|
170
|
+
export function grafanaVolumes(props: Pick<GrafanaConfigMapsProps, "entities" | "name">, options: GrafanaVolumesOptions = {}): GrafanaVolumes {
|
|
171
|
+
const prefix = dnsName(props.name ?? "grafana");
|
|
172
|
+
const layout = grafanaConfigMapLayout({ ...props, name: prefix });
|
|
173
|
+
const vol = dnsName(options.volumeName ?? prefix);
|
|
174
|
+
const root = (options.dashboardsPath ?? DEFAULT_DASHBOARDS_PATH).replace(/\/+$/, "");
|
|
175
|
+
const volumes: GrafanaVolumes["volumes"] = [];
|
|
176
|
+
const volumeMounts: GrafanaVolumes["volumeMounts"] = [];
|
|
177
|
+
|
|
178
|
+
const provisioning = [
|
|
179
|
+
...(layout.datasources ? [{ configMap: { name: layout.datasources.configMap, items: [{ key: layout.datasources.key, path: `datasources/${layout.datasources.key}` }] } }] : []),
|
|
180
|
+
...(layout.providers ? [{ configMap: { name: layout.providers.configMap, items: [{ key: layout.providers.key, path: `dashboards/${layout.providers.key}` }] } }] : []),
|
|
181
|
+
];
|
|
182
|
+
if (provisioning.length > 0) {
|
|
183
|
+
volumes.push({ name: `${vol}-provisioning`, projected: { sources: provisioning } });
|
|
184
|
+
volumeMounts.push({ name: `${vol}-provisioning`, mountPath: GRAFANA_PROVISIONING_PATH, readOnly: true });
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const folders = [...new Set(layout.dashboards.map((d) => d.folder))].sort();
|
|
188
|
+
folders.forEach((folder, i) => {
|
|
189
|
+
const name = `${vol}-dashboards-${i}`;
|
|
190
|
+
const sources = layout.dashboards
|
|
191
|
+
.filter((d) => d.folder === folder)
|
|
192
|
+
.map((d) => ({ configMap: { name: d.configMap, items: [{ key: d.key, path: d.key }] } }));
|
|
193
|
+
volumes.push({ name, projected: { sources } });
|
|
194
|
+
volumeMounts.push({ name, mountPath: folder ? `${root}/${folder}` : root, readOnly: true });
|
|
195
|
+
});
|
|
196
|
+
return { volumes, volumeMounts };
|
|
197
|
+
}
|
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
* The grafana lexicon's chant audit catalog, contributed via
|
|
3
3
|
* `grafanaPlugin.auditCatalog()` (#687, #1346).
|
|
4
4
|
*
|
|
5
|
-
* GRAF101-
|
|
5
|
+
* GRAF101-GRAF109, GRAF111-GRAF114 and GRAF115 read the emitted dashboard
|
|
6
|
+
* JSON and provisioning files (alerting included), so
|
|
6
7
|
* they fire on an audit of files chant didn't build too, and are
|
|
7
8
|
* `yamlBased`. GRAF001 and GRAF002 read TypeScript source, so they are
|
|
8
9
|
* constructed with `yamlBased: false`; they are listed for a reader who
|
|
@@ -25,7 +26,7 @@ export const grafanaAuditCatalog: Record<string, RuleMeta> = {
|
|
|
25
26
|
GRAF002: sourceRule(
|
|
26
27
|
"GRAF002",
|
|
27
28
|
"security",
|
|
28
|
-
"Datasource secret declared as a literal",
|
|
29
|
+
"Datasource or contact point secret declared as a literal",
|
|
29
30
|
"Replace the value with `$__env{NAME}` or `$__file{/path}` so Grafana reads it when it loads the provisioning file.",
|
|
30
31
|
),
|
|
31
32
|
GRAF101: auditRule(
|
|
@@ -33,7 +34,7 @@ export const grafanaAuditCatalog: Record<string, RuleMeta> = {
|
|
|
33
34
|
"merge-worthy",
|
|
34
35
|
"guidance",
|
|
35
36
|
"Panel uses an undeclared datasource",
|
|
36
|
-
"Point the panel or query at a declared Datasource, or declare the datasource it names in the same build root.",
|
|
37
|
+
"Point the panel or query at a declared Datasource, or declare the datasource it names in the same build root: a Datasource to provision it, an ExternalDatasource if it already exists in Grafana.",
|
|
37
38
|
{ category: "correctness" },
|
|
38
39
|
),
|
|
39
40
|
GRAF102: auditRule(
|
|
@@ -84,4 +85,60 @@ export const grafanaAuditCatalog: Record<string, RuleMeta> = {
|
|
|
84
85
|
"Fix the key or value the message names; the panel option types generated from the same schema show what is allowed.",
|
|
85
86
|
{ category: "correctness" },
|
|
86
87
|
),
|
|
88
|
+
GRAF108: auditRule(
|
|
89
|
+
"GRAF108",
|
|
90
|
+
"merge-worthy",
|
|
91
|
+
"guidance",
|
|
92
|
+
"Query sent to Prometheus is not valid PromQL",
|
|
93
|
+
"Fix the expression at the offset the message names: an unbalanced bracket or quote, a bad range duration, a missing operator. Template variables are substituted before the check, so $var, ${var} and $__rate_interval are fine where Grafana accepts them.",
|
|
94
|
+
{ category: "correctness" },
|
|
95
|
+
),
|
|
96
|
+
GRAF109: auditRule(
|
|
97
|
+
"GRAF109",
|
|
98
|
+
"merge-worthy",
|
|
99
|
+
"guidance",
|
|
100
|
+
"Dashboard provisioning puts a dashboard somewhere other than its declared folder, or loads it twice",
|
|
101
|
+
"Give each dashboard provider its own path; set foldersFromFilesStructure: true (and no folder) on the provider when dashboards declare a folder; for a nested folder, run Grafana 13.1 or later or use a single-level folder.",
|
|
102
|
+
{ category: "correctness" },
|
|
103
|
+
),
|
|
104
|
+
GRAF111: auditRule(
|
|
105
|
+
"GRAF111",
|
|
106
|
+
"merge-worthy",
|
|
107
|
+
"guidance",
|
|
108
|
+
"Alert rule condition, expression input or refId does not fit the rule's queries",
|
|
109
|
+
"Point the condition, each expression's input and record.from at a refId the rule has, give each query its own refId, and fix the expression field the message names.",
|
|
110
|
+
{ category: "correctness" },
|
|
111
|
+
),
|
|
112
|
+
GRAF112: auditRule(
|
|
113
|
+
"GRAF112",
|
|
114
|
+
"merge-worthy",
|
|
115
|
+
"guidance",
|
|
116
|
+
"Alert rule queries an undeclared datasource, or one of another type",
|
|
117
|
+
"Pass a declared Datasource or ExternalDatasource as the query's datasource, of the type its model is for.",
|
|
118
|
+
{ category: "correctness" },
|
|
119
|
+
),
|
|
120
|
+
GRAF113: auditRule(
|
|
121
|
+
"GRAF113",
|
|
122
|
+
"merge-worthy",
|
|
123
|
+
"guidance",
|
|
124
|
+
"Alerting routes to an undeclared contact point or mute timing, or a matcher does not parse",
|
|
125
|
+
"Declare the ContactPoint or MuteTiming (or pass the entity itself rather than its name), and write matchers as [label, op, value] with op =, !=, =~ or !~.",
|
|
126
|
+
{ category: "correctness" },
|
|
127
|
+
),
|
|
128
|
+
GRAF114: auditRule(
|
|
129
|
+
"GRAF114",
|
|
130
|
+
"merge-worthy",
|
|
131
|
+
"guidance",
|
|
132
|
+
"Alerting uid, name, title or interval Grafana rejects, or declared twice",
|
|
133
|
+
'Use 1-40 letters, digits, "-" and "_" for uids, a multiple of 10s for group intervals, and one name per rule group, contact point, mute timing and template in each organisation.',
|
|
134
|
+
{ category: "correctness" },
|
|
135
|
+
),
|
|
136
|
+
GRAF115: auditRule(
|
|
137
|
+
"GRAF115",
|
|
138
|
+
"merge-worthy",
|
|
139
|
+
"guidance",
|
|
140
|
+
"Panel unit Grafana doesn't know",
|
|
141
|
+
'Use a unit id from Grafana\'s unit picker ("bytes", "s", "percent", "reqps"), or a custom unit such as "suffix: cores", "prefix:$", "si:mF", "count:reqs" or "currency:EUR". Grafana shows an unknown unit as a literal suffix.',
|
|
142
|
+
{ category: "correctness" },
|
|
143
|
+
),
|
|
87
144
|
};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { copyFileSync, mkdtempSync, mkdirSync, rmSync, writeFileSync } from "fs";
|
|
3
|
+
import { tmpdir } from "os";
|
|
4
|
+
import { join } from "path";
|
|
5
|
+
import { lintCommand } from "@intentius/chant/cli/commands/lint";
|
|
6
|
+
import { build } from "@intentius/chant/build";
|
|
7
|
+
import { grafanaPlugin } from "../plugin";
|
|
8
|
+
import { grafanaSerializer } from "../serializer";
|
|
9
|
+
import { BUILTIN_CATALOG } from "../catalog";
|
|
10
|
+
|
|
11
|
+
// The fixture carries no chant.config of its own, which would make it a
|
|
12
|
+
// project in the repo's workspace proposal; the lint test gives it one in a
|
|
13
|
+
// temporary directory.
|
|
14
|
+
const fixture = join(import.meta.dirname, "..", "..", "test", "fixtures", "inline-dashboard");
|
|
15
|
+
|
|
16
|
+
describe("core lint on grafana's property-kind declarables (chant #2957)", () => {
|
|
17
|
+
it("names every panel, row, query and variable class property-kind, and no resource", () => {
|
|
18
|
+
const names = grafanaPlugin.propertyClassNames!();
|
|
19
|
+
expect(names).toEqual(expect.arrayContaining(["TimeSeriesPanel", "StatPanel", "Row", "PromQuery", "LokiQuery", "QueryVariable"]));
|
|
20
|
+
for (const resource of ["Dashboard", "Datasource", "DashboardProvider"]) expect(names).not.toContain(resource);
|
|
21
|
+
expect(names).toHaveLength(BUILTIN_CATALOG.filter((e) => e.entityKind === "property").length);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it("passes COR001, COR004 and COR009 on a dashboard written inline in one file", async () => {
|
|
25
|
+
const project = mkdtempSync(join(tmpdir(), "chant-grafana-lint-"));
|
|
26
|
+
try {
|
|
27
|
+
writeFileSync(join(project, "chant.config.json"), JSON.stringify({ lexicons: ["grafana"] }));
|
|
28
|
+
mkdirSync(join(project, "src"));
|
|
29
|
+
copyFileSync(join(fixture, "dashboard.ts"), join(project, "src", "dashboard.ts"));
|
|
30
|
+
const result = await lintCommand({ path: join(project, "src"), format: "stylish" });
|
|
31
|
+
expect(result.output).not.toMatch(/COR00[149]|LEX001/);
|
|
32
|
+
expect(result.warningCount).toBe(0);
|
|
33
|
+
expect(result.errorCount).toBe(0);
|
|
34
|
+
} finally {
|
|
35
|
+
rmSync(project, { recursive: true, force: true });
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it("and that file builds", async () => {
|
|
40
|
+
const result = await build(fixture, [grafanaSerializer]);
|
|
41
|
+
expect(result.errors).toEqual([]);
|
|
42
|
+
});
|
|
43
|
+
});
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* GRAF101: A panel, query or variable uses a datasource no Datasource in the build declares
|
|
2
|
+
* GRAF101: A panel, query or variable uses a datasource no Datasource or ExternalDatasource in the build declares
|
|
3
3
|
*
|
|
4
|
-
* Grafana shows "datasource not found" on the panel. Typed references cannot go wrong this way, so it mostly catches a { type, uid } ref or a hand-edited dashboard. A panel with queries and no datasource at all is a warning: Grafana sends them to its default datasource. This check joins dashboards against the
|
|
4
|
+
* Grafana shows "datasource not found" on the panel. Typed references cannot go wrong this way, so it mostly catches a { type, uid } ref or a hand-edited dashboard. A datasource variable whose plugin type no declared datasource has is an error too. A panel with queries and no datasource at all is a warning: Grafana sends them to its default datasource. This check joins dashboards against the datasources of the same build root (chant #1939): declare one that exists in Grafana but is provisioned elsewhere with ExternalDatasource, which the check counts and the build never provisions. A build that declares no datasource at all gets one warning per dashboard that references any, since nothing can be checked.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
|
|
@@ -9,7 +9,7 @@ import { grafanaDiagnostics } from "./grafana-helpers";
|
|
|
9
9
|
|
|
10
10
|
export const graf101: PostSynthCheck = {
|
|
11
11
|
id: "GRAF101",
|
|
12
|
-
description: "A panel, query or variable uses a datasource no Datasource in the build declares",
|
|
12
|
+
description: "A panel, query or variable uses a datasource no Datasource or ExternalDatasource in the build declares",
|
|
13
13
|
|
|
14
14
|
check(ctx: PostSynthContext): PostSynthDiagnostic[] {
|
|
15
15
|
return grafanaDiagnostics(ctx, "GRAF101");
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* GRAF102: A query is sent to a datasource of another plugin type
|
|
3
3
|
*
|
|
4
|
-
* A PromQL query sent to Tempo fails at query time. The typed classes prevent this at compile time; the check catches refs, datasource variables of the wrong plugin type and hand-edited dashboards. Like GRAF101 it joins against the Datasource declarations of the same build root
|
|
4
|
+
* A PromQL query sent to Tempo fails at query time. The typed classes prevent this at compile time; the check catches refs, datasource variables of the wrong plugin type and hand-edited dashboards. Like GRAF101 it joins against the Datasource and ExternalDatasource declarations of the same build root (chant #1939).
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* GRAF107: A dashboard does not match the pinned Grafana schema
|
|
3
3
|
*
|
|
4
|
-
* The dashboard is validated against the dashboard schema at GRAFANA_SCHEMA_PIN, and each panel's options, fieldConfig.defaults.custom and queries against their plugin's schema with required fields relaxed (Grafana fills those in).
|
|
4
|
+
* The dashboard is validated against the dashboard schema at GRAFANA_SCHEMA_PIN, with the correction overlay in src/spec/overlay/ applied, and each panel's options, fieldConfig.defaults.custom and queries against their plugin's schema with required fields relaxed (Grafana fills those in). Library panel models embedded in an export's __elements are checked the same way. A value the schema does not allow is an error: the edits Grafana would otherwise need on import. A key the schema does not know is a warning, since newer Grafana versions add keys before the pin catches up.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GRAF108: A query sent to Prometheus is not valid PromQL
|
|
3
|
+
*
|
|
4
|
+
* Each panel query, query variable and alert rule query whose datasource resolves to a prometheus datasource is parsed with the prometheus lexicon's checkPromql (@prometheus-io/lezer-promql), after its template variables ($var, ${var}, [[var]], $__interval, $__rate_interval, $__range and the other $__ macros) are replaced with placeholders that parse where they stand. A query whose datasource can't be told is not parsed; an alert rule query is parsed when its datasourceUid is a declared prometheus datasource or its model says `datasource.type: prometheus`. It is a syntax check: an unbalanced bracket or a bad duration fails, rate over an instant vector does not.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
|
|
8
|
+
import { grafanaDiagnostics } from "./grafana-helpers";
|
|
9
|
+
|
|
10
|
+
export const graf108: PostSynthCheck = {
|
|
11
|
+
id: "GRAF108",
|
|
12
|
+
description: "A query sent to Prometheus is not valid PromQL",
|
|
13
|
+
|
|
14
|
+
check(ctx: PostSynthContext): PostSynthDiagnostic[] {
|
|
15
|
+
return grafanaDiagnostics(ctx, "GRAF108");
|
|
16
|
+
},
|
|
17
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GRAF109: Dashboard provisioning puts a dashboard somewhere other than its declared folder, or loads it twice
|
|
3
|
+
*
|
|
4
|
+
* Read from the dashboard provisioning file and where each dashboard file sits under dashboards/. Two providers in one org whose paths are the same or nested load every dashboard twice, and Grafana then takes database writes away from both: an error. A provider that sets folder and folderUid with foldersFromFilesStructure is refused by Grafana: an error; with one of them, that folder is ignored: a warning. Dashboards that declare a folder when no provider sets foldersFromFilesStructure all land in the provider's folder: a warning. A nested folder ("Platform/Kubernetes") is a warning: Grafana 13.1 and later nest the folders, 12.4 and 13.0 use only the last level; past Grafana's default depth of 4 it says so, and past its hard limit of 7 it is an error.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
|
|
8
|
+
import { grafanaDiagnostics } from "./grafana-helpers";
|
|
9
|
+
|
|
10
|
+
export const graf109: PostSynthCheck = {
|
|
11
|
+
id: "GRAF109",
|
|
12
|
+
description: "Dashboard provisioning puts a dashboard somewhere other than its declared folder, or loads it twice",
|
|
13
|
+
|
|
14
|
+
check(ctx: PostSynthContext): PostSynthDiagnostic[] {
|
|
15
|
+
return grafanaDiagnostics(ctx, "GRAF109");
|
|
16
|
+
},
|
|
17
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GRAF111: An alert rule's condition, expression inputs or refIds do not fit together
|
|
3
|
+
*
|
|
4
|
+
* Each alert rule's condition, each server-side expression's inputs (a reduce, threshold or resample `expression`, the `$A` references of a math expression, a classic condition's query) and a recording rule's record.from must name a refId of the same rule; refIds must be unique, and a rule needs a condition unless it records. Each expression model is also validated against the pinned `expr` schema (a missing reducer, an unknown evaluator type). Grafana refuses such a rule, and a refused rule stops it provisioning every alerting file at startup.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
|
|
8
|
+
import { grafanaDiagnostics } from "./grafana-helpers";
|
|
9
|
+
|
|
10
|
+
export const graf111: PostSynthCheck = {
|
|
11
|
+
id: "GRAF111",
|
|
12
|
+
description: "An alert rule's condition, expression inputs or refIds do not fit together",
|
|
13
|
+
|
|
14
|
+
check(ctx: PostSynthContext): PostSynthDiagnostic[] {
|
|
15
|
+
return grafanaDiagnostics(ctx, "GRAF111");
|
|
16
|
+
},
|
|
17
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GRAF112: An alert rule queries a datasource the build does not declare, or of another type
|
|
3
|
+
*
|
|
4
|
+
* Each alert rule query's datasourceUid must be a datasource the build declares: a Datasource it provisions or an ExternalDatasource that already exists in Grafana. A query whose model states a datasource type (model.datasource.type) must match the declared type, and a recording rule's targetDatasourceUid must be a declared Prometheus datasource. With no datasource declared at all, it warns once that it cannot check.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
|
|
8
|
+
import { grafanaDiagnostics } from "./grafana-helpers";
|
|
9
|
+
|
|
10
|
+
export const graf112: PostSynthCheck = {
|
|
11
|
+
id: "GRAF112",
|
|
12
|
+
description: "An alert rule queries a datasource the build does not declare, or of another type",
|
|
13
|
+
|
|
14
|
+
check(ctx: PostSynthContext): PostSynthDiagnostic[] {
|
|
15
|
+
return grafanaDiagnostics(ctx, "GRAF112");
|
|
16
|
+
},
|
|
17
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GRAF113: Alerting routes to a contact point or mute timing the build does not declare, or a policy matcher does not parse
|
|
3
|
+
*
|
|
4
|
+
* The notification policy tree's receivers, every rule's notification_settings.receiver, and each mute_time_intervals and active_time_intervals entry must name a ContactPoint or MuteTiming the build declares (grafana-default-email exists in every Grafana). Policy object_matchers must be [label, op, value] with op =, !=, =~ or !~ and a valid regex; matchers must parse as Alertmanager matchers. With no contact point or mute timing declared it warns once that it cannot check.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
|
|
8
|
+
import { grafanaDiagnostics } from "./grafana-helpers";
|
|
9
|
+
|
|
10
|
+
export const graf113: PostSynthCheck = {
|
|
11
|
+
id: "GRAF113",
|
|
12
|
+
description: "Alerting routes to a contact point or mute timing the build does not declare, or a policy matcher does not parse",
|
|
13
|
+
|
|
14
|
+
check(ctx: PostSynthContext): PostSynthDiagnostic[] {
|
|
15
|
+
return grafanaDiagnostics(ctx, "GRAF113");
|
|
16
|
+
},
|
|
17
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GRAF114: An alerting uid, name, title or interval Grafana rejects, or one declared twice
|
|
3
|
+
*
|
|
4
|
+
* Rule uids and contact point receiver uids must be 1-40 letters, digits, - and _; rules need a title of at most 190 characters; groups need a name, a folder and an interval that is a positive multiple of 10s; for and keepFiringFor must be durations; noDataState and execErrState must be values Grafana knows. Rule uids, group names within a folder, contact point names, receiver uids, mute timing and template names must be unique per organisation, and each organisation has one policy tree. Grafana refuses the file otherwise, and one refused file stops it provisioning every alerting file.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
|
|
8
|
+
import { grafanaDiagnostics } from "./grafana-helpers";
|
|
9
|
+
|
|
10
|
+
export const graf114: PostSynthCheck = {
|
|
11
|
+
id: "GRAF114",
|
|
12
|
+
description: "An alerting uid, name, title or interval Grafana rejects, or one declared twice",
|
|
13
|
+
|
|
14
|
+
check(ctx: PostSynthContext): PostSynthDiagnostic[] {
|
|
15
|
+
return grafanaDiagnostics(ctx, "GRAF114");
|
|
16
|
+
},
|
|
17
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GRAF115: A unit Grafana doesn't know
|
|
3
|
+
*
|
|
4
|
+
* Each panel's `fieldConfig.defaults.unit`, every `unit` override, a heatmap's `options.yAxis.unit` and `options.cellValues.unit`, and a legacy graph panel's `yaxes[].format` are looked up in Grafana's unit registry at v13.2.2 (`src/spec/units.gen.ts`). Custom units (`prefix:`, `suffix:`, `time:`, `si:`, `count:`, `currency:`, `bool:`) pass. Anything else Grafana shows as a literal suffix, so it is a warning that names the closest registered id.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
|
|
8
|
+
import { grafanaDiagnostics } from "./grafana-helpers";
|
|
9
|
+
|
|
10
|
+
export const graf115: PostSynthCheck = {
|
|
11
|
+
id: "GRAF115",
|
|
12
|
+
description: "A panel unit Grafana doesn't know, which it shows as a literal suffix",
|
|
13
|
+
|
|
14
|
+
check(ctx: PostSynthContext): PostSynthDiagnostic[] {
|
|
15
|
+
return grafanaDiagnostics(ctx, "GRAF115");
|
|
16
|
+
},
|
|
17
|
+
};
|
|
@@ -1,18 +1,21 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Shared plumbing for the grafana post-synth checks: find the dashboards
|
|
3
|
-
* provisioned datasources
|
|
4
|
-
*
|
|
2
|
+
* Shared plumbing for the grafana post-synth checks: find the dashboards,
|
|
3
|
+
* provisioned datasources, dashboard providers and alerting provisioning
|
|
4
|
+
* files in a build's output, and run the plain-function checks in
|
|
5
|
+
* `validate-output.ts` over them.
|
|
5
6
|
*
|
|
6
7
|
* Any output file shaped like a dashboard or a datasource provisioning file
|
|
7
8
|
* counts, not only the grafana lexicon's own, so a dashboard another lexicon
|
|
8
|
-
* embeds is checked the same way.
|
|
9
|
+
* embeds is checked the same way. `ExternalDatasource` declarations are never
|
|
10
|
+
* written to a provisioning file, so they come from the build's entities.
|
|
9
11
|
*/
|
|
10
12
|
|
|
11
13
|
import type { PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
|
|
12
14
|
import type { SerializerResult } from "@intentius/chant/serializer";
|
|
13
15
|
import { loadAll } from "js-yaml";
|
|
14
|
-
import { looksLikeDashboard, looksLikeDatasourceProvisioning } from "../../detect";
|
|
15
|
-
import type
|
|
16
|
+
import { looksLikeAlertingProvisioning, looksLikeDashboard, looksLikeDashboardProvisioning, looksLikeDatasourceProvisioning } from "../../detect";
|
|
17
|
+
import { externalDatasourceRecord, type ProvisionedDatasource } from "../../build";
|
|
18
|
+
import { isExternalDatasource } from "../../datasource";
|
|
16
19
|
import { issuesFor, type GrafanaArtifacts, type GrafanaIssueCode } from "../../validate-output";
|
|
17
20
|
|
|
18
21
|
function parse(text: string, name: string): unknown[] {
|
|
@@ -30,9 +33,10 @@ function parse(text: string, name: string): unknown[] {
|
|
|
30
33
|
}
|
|
31
34
|
}
|
|
32
35
|
|
|
33
|
-
/** Every dashboard
|
|
36
|
+
/** Every dashboard, provisioned datasource and dashboard provider and alerting provisioning file in the build's output, and every `ExternalDatasource` it declares. */
|
|
34
37
|
export function grafanaArtifacts(ctx: PostSynthContext): GrafanaArtifacts {
|
|
35
|
-
const
|
|
38
|
+
const externalDatasources = [...(ctx.entities?.values() ?? [])].filter(isExternalDatasource).map(externalDatasourceRecord);
|
|
39
|
+
const out: GrafanaArtifacts = { dashboards: [], datasources: [], externalDatasources, providers: [], alerting: [] };
|
|
36
40
|
for (const [lexicon, output] of ctx.outputs) {
|
|
37
41
|
const texts: Array<[string, string]> =
|
|
38
42
|
typeof output === "string"
|
|
@@ -42,6 +46,9 @@ export function grafanaArtifacts(ctx: PostSynthContext): GrafanaArtifacts {
|
|
|
42
46
|
if (!text) continue;
|
|
43
47
|
for (const doc of parse(text, source)) {
|
|
44
48
|
if (looksLikeDashboard(doc)) out.dashboards.push({ source, json: doc });
|
|
49
|
+
else if (looksLikeDashboardProvisioning(doc)) {
|
|
50
|
+
for (const p of doc.providers) if (p && typeof p === "object" && !Array.isArray(p)) out.providers!.push(p as Record<string, unknown>);
|
|
51
|
+
} else if (looksLikeAlertingProvisioning(doc)) out.alerting!.push({ source, json: doc });
|
|
45
52
|
else if (looksLikeDatasourceProvisioning(doc)) {
|
|
46
53
|
for (const d of doc.datasources) {
|
|
47
54
|
if (d && typeof d === "object" && typeof (d as ProvisionedDatasource).name === "string") {
|
|
@@ -7,6 +7,13 @@ import { graf104 } from "./graf104";
|
|
|
7
7
|
import { graf105 } from "./graf105";
|
|
8
8
|
import { graf106 } from "./graf106";
|
|
9
9
|
import { graf107 } from "./graf107";
|
|
10
|
+
import { graf108 } from "./graf108";
|
|
11
|
+
import { graf109 } from "./graf109";
|
|
12
|
+
import { graf111 } from "./graf111";
|
|
13
|
+
import { graf112 } from "./graf112";
|
|
14
|
+
import { graf113 } from "./graf113";
|
|
15
|
+
import { graf114 } from "./graf114";
|
|
16
|
+
import { graf115 } from "./graf115";
|
|
10
17
|
|
|
11
18
|
export const postSynthChecks: PostSynthCheck[] = [
|
|
12
19
|
graf101,
|
|
@@ -16,4 +23,11 @@ export const postSynthChecks: PostSynthCheck[] = [
|
|
|
16
23
|
graf105,
|
|
17
24
|
graf106,
|
|
18
25
|
graf107,
|
|
26
|
+
graf108,
|
|
27
|
+
graf109,
|
|
28
|
+
graf111,
|
|
29
|
+
graf112,
|
|
30
|
+
graf113,
|
|
31
|
+
graf114,
|
|
32
|
+
graf115,
|
|
19
33
|
];
|