@intentius/chant-lexicon-grafana 0.98.0 → 0.100.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 +24 -8
- 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/annotations.d.ts +61 -0
- package/dist/annotations.d.ts.map +1 -0
- package/dist/api/apply.d.ts +160 -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 +63 -0
- package/dist/api/fake-grafana.d.ts.map +1 -0
- package/dist/api/folders.d.ts +146 -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 +55 -6
- 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/generate.d.ts +7 -1
- package/dist/codegen/generate.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 +11 -3
- 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 +51 -9
- package/dist/dashboard.d.ts.map +1 -1
- package/dist/datasource-refs.d.ts +99 -0
- package/dist/datasource-refs.d.ts.map +1 -0
- package/dist/datasource-settings.d.ts +834 -0
- package/dist/datasource-settings.d.ts.map +1 -0
- package/dist/datasource.d.ts +82 -11
- 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 +47 -0
- package/dist/deep-observe.d.ts.map +1 -0
- package/dist/describe-resources.d.ts +50 -0
- package/dist/describe-resources.d.ts.map +1 -0
- package/dist/detect.d.ts +32 -3
- 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/folder.d.ts +68 -0
- package/dist/folder.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 +89 -0
- package/dist/import/mappings.d.ts.map +1 -0
- package/dist/import/model.d.ts +105 -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 +125 -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 +41 -0
- package/dist/import/testdata/fixtures.d.ts.map +1 -0
- package/dist/import/v2.d.ts +73 -0
- package/dist/import/v2.d.ts.map +1 -0
- package/dist/index.d.ts +31 -12
- package/dist/index.d.ts.map +1 -1
- package/dist/init-templates.d.ts +27 -0
- package/dist/init-templates.d.ts.map +1 -0
- package/dist/integrity.json +22 -12
- 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/graf104.d.ts +2 -2
- package/dist/lint/post-synth/graf106.d.ts +2 -2
- 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/graf110.d.ts +8 -0
- package/dist/lint/post-synth/graf110.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 +205 -0
- package/dist/okf/index.md +53 -4
- 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/GRAF104.md +2 -2
- package/dist/okf/rules/GRAF106.md +2 -2
- package/dist/okf/rules/GRAF108.md +11 -0
- package/dist/okf/rules/GRAF109.md +15 -0
- package/dist/okf/rules/GRAF110.md +11 -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/AdhocVariable.md +9 -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/AzureMonitorQuery.md +9 -0
- package/dist/okf/types/BarChartPanel.md +9 -0
- package/dist/okf/types/BarGaugePanel.md +9 -0
- package/dist/okf/types/BigQueryQuery.md +9 -0
- package/dist/okf/types/CanvasPanel.md +9 -0
- package/dist/okf/types/ClassicConditionsExpression.md +9 -0
- package/dist/okf/types/CloudMonitoringQuery.md +9 -0
- package/dist/okf/types/CloudWatchQuery.md +9 -0
- package/dist/okf/types/ContactPoint.md +14 -0
- package/dist/okf/types/Dashboard.md +2 -1
- package/dist/okf/types/Datasource.md +3 -2
- package/dist/okf/types/DatasourceProvisioning.md +9 -0
- package/dist/okf/types/ElasticsearchQuery.md +9 -0
- package/dist/okf/types/ExternalDatasource.md +15 -0
- package/dist/okf/types/FlameGraphPanel.md +9 -0
- package/dist/okf/types/Folder.md +9 -0
- package/dist/okf/types/GeomapPanel.md +9 -0
- package/dist/okf/types/GroupByVariable.md +9 -0
- package/dist/okf/types/HistogramPanel.md +9 -0
- package/dist/okf/types/MSSQLQuery.md +9 -0
- package/dist/okf/types/MathExpression.md +9 -0
- package/dist/okf/types/MuteTiming.md +13 -0
- package/dist/okf/types/MySQLQuery.md +9 -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/PostgresQuery.md +9 -0
- package/dist/okf/types/PyroscopeQuery.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/SwitchVariable.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 +86 -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 +78 -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-models.d.ts +95 -0
- package/dist/query-models.d.ts.map +1 -0
- package/dist/query.d.ts +116 -18
- 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/graf104.ts +3 -3
- package/dist/rules/graf106.ts +3 -3
- package/dist/rules/graf107.ts +1 -1
- package/dist/rules/graf108.ts +17 -0
- package/dist/rules/graf109.ts +17 -0
- package/dist/rules/graf110.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 +27 -10
- package/dist/rules/literal-secret.ts +60 -6
- package/dist/schema/azuremonitor.gen.d.ts +354 -0
- package/dist/schema/azuremonitor.gen.d.ts.map +1 -0
- 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/bigquery.gen.d.ts +93 -0
- package/dist/schema/bigquery.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/cloudwatch.gen.d.ts +394 -0
- package/dist/schema/cloudwatch.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/elasticsearch.gen.d.ts +444 -0
- package/dist/schema/elasticsearch.gen.d.ts.map +1 -0
- 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/googlecloudmonitoring.gen.d.ts +312 -0
- package/dist/schema/googlecloudmonitoring.gen.d.ts.map +1 -0
- package/dist/schema/grafanapyroscope.gen.d.ts +60 -0
- package/dist/schema/grafanapyroscope.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 +18 -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 +35 -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 +39 -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-operations.md +108 -0
- package/dist/skills/chant-grafana-provisioning.md +3 -3
- package/dist/skills/chant-grafana.md +15 -8
- 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 +21 -13
- package/dist/spec/schemas.d.ts.map +1 -1
- package/dist/spec/schemas.gen.d.ts +3 -0
- package/dist/spec/schemas.gen.d.ts.map +1 -0
- 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 +62 -9
- package/dist/validate-output.d.ts.map +1 -1
- package/dist/validate.d.ts.map +1 -1
- package/dist/validation.d.ts +14 -0
- package/dist/validation.d.ts.map +1 -0
- package/dist/variables.d.ts +109 -7
- 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/annotations.ts +84 -0
- package/src/api/apply.test.ts +256 -0
- package/src/api/apply.ts +394 -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 +190 -0
- package/src/api/folders.ts +339 -0
- package/src/api/library-panels.ts +101 -0
- package/src/build.test.ts +351 -10
- package/src/build.ts +294 -55
- package/src/catalog.ts +104 -2
- package/src/codegen/docs.ts +50 -10
- package/src/codegen/generate.ts +27 -1
- 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 +14 -2
- 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 +68 -9
- package/src/datasource-refs.test.ts +56 -0
- package/src/datasource-refs.ts +213 -0
- package/src/datasource-settings.test.ts +106 -0
- package/src/datasource-settings.ts +941 -0
- package/src/datasource.ts +123 -11
- package/src/deep-observe-hooks.ts +267 -0
- package/src/deep-observe.test.ts +311 -0
- package/src/deep-observe.ts +187 -0
- package/src/describe-resources.test.ts +271 -0
- package/src/describe-resources.ts +244 -0
- package/src/detect.ts +82 -5
- package/src/export-resources.test.ts +129 -0
- package/src/export-resources.ts +84 -0
- package/src/folder.ts +118 -0
- package/src/generated/lexicon-grafana.json +205 -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 +130 -0
- package/src/import/generator.test.ts +210 -0
- package/src/import/generator.ts +463 -0
- package/src/import/live-export.ts +264 -0
- package/src/import/mappings.ts +336 -0
- package/src/import/model.ts +117 -0
- package/src/import/normalize-alerting.ts +101 -0
- package/src/import/normalize.test.ts +117 -0
- package/src/import/normalize.ts +393 -0
- package/src/import/parser.test.ts +516 -0
- package/src/import/parser.ts +1387 -0
- package/src/import/report.ts +62 -0
- package/src/import/roundtrip.test.ts +563 -0
- package/src/import/testdata/fixtures.ts +137 -0
- package/src/import/v2.test.ts +292 -0
- package/src/import/v2.ts +804 -0
- package/src/import.e2e.test.ts +31 -84
- package/src/import.test.ts +9 -3
- package/src/index.ts +308 -14
- package/src/init-templates.test.ts +74 -0
- package/src/init-templates.ts +349 -0
- package/src/k8s.test.ts +98 -0
- package/src/k8s.ts +197 -0
- package/src/lint/audit-catalog.ts +68 -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/graf104.ts +3 -3
- package/src/lint/post-synth/graf106.ts +3 -3
- 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/graf110.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 +27 -10
- package/src/lint/post-synth/index.ts +16 -0
- package/src/lint/post-synth/post-synth.test.ts +333 -12
- package/src/lint/rules/literal-secret.ts +60 -6
- package/src/lint/rules/rules.test.ts +27 -0
- package/src/load-cost.test.ts +35 -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 +184 -0
- package/src/op/activities/grafana-apply.test.ts +243 -0
- package/src/op/activities/grafana-apply.ts +197 -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 +122 -0
- package/src/panel-options.ts +66 -0
- package/src/panels.test.ts +53 -0
- package/src/panels.ts +148 -10
- package/src/pin.ts +69 -6
- package/src/plugin.test.ts +12 -2
- package/src/plugin.ts +75 -0
- package/src/promql-check.test.ts +108 -0
- package/src/promql-check.ts +193 -0
- package/src/query-models.ts +94 -0
- package/src/query.test.ts +166 -0
- package/src/query.ts +190 -20
- package/src/schema/azuremonitor.gen.ts +381 -0
- package/src/schema/barchart.gen.ts +221 -0
- package/src/schema/bargauge.gen.ts +111 -0
- package/src/schema/bigquery.gen.ts +110 -0
- package/src/schema/canvas.gen.ts +176 -0
- package/src/schema/cloudwatch.gen.ts +423 -0
- package/src/schema/dashboard.gen.ts +206 -22
- package/src/schema/elasticsearch.gen.ts +508 -0
- 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/googlecloudmonitoring.gen.ts +330 -0
- package/src/schema/grafanapyroscope.gen.ts +66 -0
- package/src/schema/heatmap.gen.ts +2 -1
- package/src/schema/histogram.gen.ts +151 -0
- package/src/schema/index.ts +18 -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 +41 -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 +199 -57
- 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 +34 -0
- package/src/skills/chant-grafana-alerting.md +49 -0
- package/src/skills/chant-grafana-operations.md +108 -0
- package/src/skills/chant-grafana-provisioning.md +3 -3
- package/src/skills/chant-grafana.md +15 -8
- 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 +103 -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/azuremonitor.jsonschema.json +679 -0
- package/src/spec/schemas/barchart.jsonschema.json +419 -0
- package/src/spec/schemas/bargauge.jsonschema.json +224 -0
- package/src/spec/schemas/bigquery.jsonschema.json +290 -0
- package/src/spec/schemas/canvas.jsonschema.json +356 -0
- package/src/spec/schemas/cloudwatch.jsonschema.json +721 -0
- package/src/spec/schemas/dashboardv2.jsonschema.json +3387 -0
- package/src/spec/schemas/elasticsearch.jsonschema.json +1695 -0
- package/src/spec/schemas/expr.jsonschema.json +987 -0
- package/src/spec/schemas/geomap.jsonschema.json +220 -0
- package/src/spec/schemas/googlecloudmonitoring.jsonschema.json +488 -0
- package/src/spec/schemas/grafanapyroscope.jsonschema.json +93 -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.gen.ts +40 -0
- package/src/spec/schemas.ts +30 -16
- 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 +430 -0
- package/src/validate-output.ts +427 -104
- package/src/validate.ts +28 -0
- package/src/validation.ts +27 -0
- package/src/variables.ts +120 -8
package/src/schema-validate.ts
CHANGED
|
@@ -1,17 +1,36 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Validate built Grafana JSON against the vendored schemas at
|
|
3
|
-
* `GRAFANA_SCHEMA_PIN`, offline.
|
|
3
|
+
* `GRAFANA_SCHEMA_PIN`, with the correction overlay applied, offline.
|
|
4
4
|
*
|
|
5
5
|
* The dashboard is checked against the dashboard schema as it stands. Panel
|
|
6
6
|
* options, `fieldConfig.defaults.custom` and each query are checked against
|
|
7
7
|
* their plugin's schema with every `required` list removed, because Grafana
|
|
8
|
-
* fills in options a panel leaves out
|
|
9
|
-
*
|
|
8
|
+
* fills in options a panel leaves out.
|
|
9
|
+
*
|
|
10
|
+
* Two kinds of problem come back. A value the schema does not allow (a
|
|
11
|
+
* wrong type, an enum value it does not list, a required field missing) is
|
|
12
|
+
* an error. A key the pinned schema does not know is a warning: Grafana
|
|
13
|
+
* adds fields every minor release and keeps what it does not understand, so
|
|
14
|
+
* an unknown key is more often a newer Grafana than a mistake. To tell the
|
|
15
|
+
* two apart each value is validated twice, once with every object opened
|
|
16
|
+
* (`additionalProperties: false` dropped; what fails is an error) and once
|
|
17
|
+
* as pinned (what fails only for an unknown key is a warning).
|
|
18
|
+
*
|
|
19
|
+
* An export shared externally embeds its library panels in `__elements`;
|
|
20
|
+
* each element's `model` is checked the same way as a panel on the grid.
|
|
21
|
+
*
|
|
22
|
+
* `oneOf` is read as `anyOf` throughout. The CUE these schemas come from
|
|
23
|
+
* has disjunctions, which accept a value matching any branch; cog writes
|
|
24
|
+
* them as `oneOf`, which rejects a value matching two, and loosely
|
|
25
|
+
* discriminated unions (a table's `cellOptions: { type: "auto" }` matches
|
|
26
|
+
* several variants) match more than one.
|
|
10
27
|
*/
|
|
11
28
|
|
|
12
|
-
import
|
|
29
|
+
import { createRequire } from "module";
|
|
30
|
+
import type AjvClass from "ajv";
|
|
31
|
+
import type { ValidateFunction, ErrorObject } from "ajv";
|
|
13
32
|
import type { SchemaName } from "./pin";
|
|
14
|
-
import {
|
|
33
|
+
import { SCHEMA_JSON } from "./spec/schemas.gen";
|
|
15
34
|
import { panelDefinitionFor } from "./panels";
|
|
16
35
|
import { queryDefinitionFor } from "./query";
|
|
17
36
|
|
|
@@ -19,35 +38,94 @@ export interface SchemaProblem {
|
|
|
19
38
|
/** JSON pointer into the dashboard. */
|
|
20
39
|
path: string;
|
|
21
40
|
message: string;
|
|
41
|
+
/** `error` for a value the schema does not allow, `warning` for a key it does not know. */
|
|
42
|
+
severity: "error" | "warning";
|
|
22
43
|
}
|
|
23
44
|
|
|
24
|
-
let ajv:
|
|
25
|
-
|
|
26
|
-
|
|
45
|
+
let ajv: AjvClass | undefined;
|
|
46
|
+
let ajvError: string | undefined;
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* ajv, loaded on first use.
|
|
50
|
+
*
|
|
51
|
+
* Every `chant build`, `chant lint` and `chant list` loads the plugin, and the
|
|
52
|
+
* plugin's post-synth barrel reaches this module, so a top-level import would
|
|
53
|
+
* put ajv on every run that has grafana installed, validating or not.
|
|
54
|
+
* `check()` is synchronous, so `await import()` is not available;
|
|
55
|
+
* `createRequire` is (the same as the cedar lexicon's wasm, and ajv is
|
|
56
|
+
* CommonJS). Where it cannot load (a bundle with no `node_modules` beside
|
|
57
|
+
* it), {@link schemaValidationUnavailable} says why and GRAF107 reports that
|
|
58
|
+
* instead of throwing.
|
|
59
|
+
*/
|
|
60
|
+
function getAjv(): AjvClass | undefined {
|
|
61
|
+
if (ajv || ajvError) return ajv;
|
|
62
|
+
try {
|
|
63
|
+
const mod = createRequire(import.meta.url)("ajv") as { default?: typeof AjvClass } & typeof AjvClass;
|
|
64
|
+
const Ajv = mod.default ?? mod;
|
|
65
|
+
ajv = new Ajv({ strict: false, allErrors: true, validateFormats: false });
|
|
66
|
+
} catch (err) {
|
|
67
|
+
ajvError = (err instanceof Error ? err.message : String(err)).split("\n")[0];
|
|
68
|
+
}
|
|
27
69
|
return ajv;
|
|
28
70
|
}
|
|
29
71
|
|
|
72
|
+
/** Why schema validation cannot run here (ajv did not load), or undefined when it can. */
|
|
73
|
+
export function schemaValidationUnavailable(): string | undefined {
|
|
74
|
+
getAjv();
|
|
75
|
+
return ajvError === undefined ? undefined : `ajv could not be loaded: ${ajvError}`;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function requireAjv(): AjvClass {
|
|
79
|
+
const a = getAjv();
|
|
80
|
+
if (!a) throw new Error(`grafana schema validation: ${schemaValidationUnavailable()}`);
|
|
81
|
+
return a;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const parsed = new Map<SchemaName, Record<string, unknown>>();
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* One schema as validation uses it: the vendored file with its overlay
|
|
88
|
+
* applied, from the generated `spec/schemas.gen.ts` (no filesystem read, so
|
|
89
|
+
* it works bundled), parsed on first use.
|
|
90
|
+
*/
|
|
91
|
+
export function bundledSchema(name: SchemaName): Record<string, unknown> {
|
|
92
|
+
let schema = parsed.get(name);
|
|
93
|
+
if (!schema) {
|
|
94
|
+
schema = JSON.parse(SCHEMA_JSON[name]) as Record<string, unknown>;
|
|
95
|
+
parsed.set(name, schema);
|
|
96
|
+
}
|
|
97
|
+
return schema;
|
|
98
|
+
}
|
|
99
|
+
|
|
30
100
|
const compiled = new Map<string, ValidateFunction>();
|
|
31
101
|
|
|
32
|
-
|
|
33
|
-
|
|
102
|
+
interface Mode {
|
|
103
|
+
/** Drop every `required` list (panel options and queries: Grafana fills them in). */
|
|
104
|
+
lenient: boolean;
|
|
105
|
+
/** Drop every `additionalProperties: false`, so unknown keys pass. */
|
|
106
|
+
open: boolean;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function prepare(node: unknown, mode: Mode): unknown {
|
|
110
|
+
if (Array.isArray(node)) return node.map((n) => prepare(n, mode));
|
|
34
111
|
if (node && typeof node === "object") {
|
|
35
112
|
const out: Record<string, unknown> = {};
|
|
36
113
|
for (const [k, v] of Object.entries(node)) {
|
|
37
|
-
if (k === "required" && Array.isArray(v)) continue;
|
|
38
|
-
|
|
114
|
+
if (mode.lenient && k === "required" && Array.isArray(v)) continue;
|
|
115
|
+
if (mode.open && k === "additionalProperties" && v === false) continue;
|
|
116
|
+
out[k === "oneOf" && Array.isArray(v) ? "anyOf" : k] = prepare(v, mode);
|
|
39
117
|
}
|
|
40
118
|
return out;
|
|
41
119
|
}
|
|
42
120
|
return node;
|
|
43
121
|
}
|
|
44
122
|
|
|
45
|
-
/** A validator for one definition of one
|
|
46
|
-
function validatorFor(name: SchemaName, def: string | undefined,
|
|
47
|
-
const key = `${name}#${def ?? ""}#${lenient}`;
|
|
123
|
+
/** A validator for one definition of one schema (the root `$ref` when `def` is left out). */
|
|
124
|
+
function validatorFor(name: SchemaName, def: string | undefined, mode: Mode): ValidateFunction | undefined {
|
|
125
|
+
const key = `${name}#${def ?? ""}#${mode.lenient}#${mode.open}`;
|
|
48
126
|
const hit = compiled.get(key);
|
|
49
127
|
if (hit) return hit;
|
|
50
|
-
const schema =
|
|
128
|
+
const schema = bundledSchema(name);
|
|
51
129
|
const definitions = (schema.definitions ?? {}) as Record<string, unknown>;
|
|
52
130
|
const ref = def ? `#/definitions/${def}` : (schema.$ref as string | undefined);
|
|
53
131
|
if (!ref) return undefined;
|
|
@@ -55,31 +133,48 @@ function validatorFor(name: SchemaName, def: string | undefined, lenient: boolea
|
|
|
55
133
|
const root: Record<string, unknown> = {
|
|
56
134
|
$schema: schema.$schema,
|
|
57
135
|
$ref: ref,
|
|
58
|
-
definitions:
|
|
136
|
+
definitions: prepare(definitions, mode),
|
|
59
137
|
};
|
|
60
|
-
const fn =
|
|
138
|
+
const fn = requireAjv().compile(root);
|
|
61
139
|
compiled.set(key, fn);
|
|
62
140
|
return fn;
|
|
63
141
|
}
|
|
64
142
|
|
|
65
|
-
function
|
|
143
|
+
function describe(e: ErrorObject): string {
|
|
144
|
+
const extra =
|
|
145
|
+
e.keyword === "enum" ? `: ${(e.params as { allowedValues: unknown[] }).allowedValues.map((v) => JSON.stringify(v)).join(", ")}` : "";
|
|
146
|
+
return `${e.message ?? "is invalid"}${extra}`;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Everything wrong with one value against one definition: errors from the
|
|
151
|
+
* opened schema, then unknown keys from the pinned one.
|
|
152
|
+
*/
|
|
153
|
+
function check(value: unknown, name: SchemaName, def: string | undefined, lenient: boolean, prefix: string): SchemaProblem[] {
|
|
154
|
+
const open = validatorFor(name, def, { lenient, open: true });
|
|
155
|
+
const closed = validatorFor(name, def, { lenient, open: false });
|
|
156
|
+
if (!open || !closed) return [];
|
|
66
157
|
const seen = new Set<string>();
|
|
67
158
|
const out: SchemaProblem[] = [];
|
|
68
|
-
|
|
69
|
-
// oneOf/anyOf wrappers repeat what their branches already said.
|
|
70
|
-
if (e.keyword === "oneOf" || e.keyword === "anyOf" || e.keyword === "if") continue;
|
|
71
|
-
const extra =
|
|
72
|
-
e.keyword === "additionalProperties"
|
|
73
|
-
? ` ("${(e.params as { additionalProperty: string }).additionalProperty}")`
|
|
74
|
-
: e.keyword === "enum"
|
|
75
|
-
? `: ${(e.params as { allowedValues: unknown[] }).allowedValues.map((v) => JSON.stringify(v)).join(", ")}`
|
|
76
|
-
: "";
|
|
77
|
-
const path = `${prefix}${e.instancePath}` || "/";
|
|
78
|
-
const message = `${e.message ?? "is invalid"}${extra}`;
|
|
159
|
+
const push = (path: string, message: string, severity: SchemaProblem["severity"]) => {
|
|
79
160
|
const k = `${path} ${message}`;
|
|
80
|
-
if (seen.has(k))
|
|
161
|
+
if (seen.has(k)) return;
|
|
81
162
|
seen.add(k);
|
|
82
|
-
out.push({ path, message });
|
|
163
|
+
out.push({ path, message, severity });
|
|
164
|
+
};
|
|
165
|
+
if (!open(value)) {
|
|
166
|
+
for (const e of open.errors ?? []) {
|
|
167
|
+
// anyOf/if wrappers repeat what their branches already said.
|
|
168
|
+
if (e.keyword === "oneOf" || e.keyword === "anyOf" || e.keyword === "if") continue;
|
|
169
|
+
push(`${prefix}${e.instancePath}` || "/", describe(e), "error");
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
if (!closed(value)) {
|
|
173
|
+
for (const e of closed.errors ?? []) {
|
|
174
|
+
if (e.keyword !== "additionalProperties") continue;
|
|
175
|
+
const key = (e.params as { additionalProperty: string }).additionalProperty;
|
|
176
|
+
push(`${prefix}${e.instancePath}` || "/", `unknown key "${key}" (not in the pinned schema)`, "warning");
|
|
177
|
+
}
|
|
83
178
|
}
|
|
84
179
|
return out;
|
|
85
180
|
}
|
|
@@ -98,46 +193,93 @@ function panelsOf(dashboard: Json): Array<{ panel: Json; path: string }> {
|
|
|
98
193
|
return out;
|
|
99
194
|
}
|
|
100
195
|
|
|
101
|
-
/**
|
|
196
|
+
/** A panel (not a row) against the dashboard's Panel definition and its plugin's options, custom field config and queries. */
|
|
197
|
+
function checkPanel(panel: Json, path: string): SchemaProblem[] {
|
|
198
|
+
const out = check(panel, "dashboard", "Panel", false, path);
|
|
199
|
+
const def = typeof panel.type === "string" ? panelDefinitionFor(panel.type) : undefined;
|
|
200
|
+
if (def?.schema) {
|
|
201
|
+
if (panel.options !== undefined) out.push(...check(panel.options, def.schema, "Options", true, `${path}/options`));
|
|
202
|
+
const custom = (panel.fieldConfig as { defaults?: { custom?: unknown } } | undefined)?.defaults?.custom;
|
|
203
|
+
if (custom !== undefined) out.push(...check(custom, def.schema, "FieldConfig", true, `${path}/fieldConfig/defaults/custom`));
|
|
204
|
+
}
|
|
205
|
+
const panelType = (panel.datasource as { type?: string } | undefined)?.type;
|
|
206
|
+
const targets = Array.isArray(panel.targets) ? (panel.targets as Json[]) : [];
|
|
207
|
+
targets.forEach((t, k) => {
|
|
208
|
+
const type = (t?.datasource as { type?: string } | undefined)?.type ?? panelType;
|
|
209
|
+
const qdef = type ? queryDefinitionFor(type) : undefined;
|
|
210
|
+
if (!qdef?.schema) return;
|
|
211
|
+
out.push(...check(t, qdef.schema, undefined, true, `${path}/targets/${k}`));
|
|
212
|
+
});
|
|
213
|
+
return out;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/** `LibraryElementKind.Panel` (public/app/features/library-panels/types.ts:8-10 at v13.2.2, its only member). */
|
|
217
|
+
const LIBRARY_PANEL_KIND = 1;
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* The library panel models an external export embeds in `__elements`. The
|
|
221
|
+
* envelope check covers each element's own fields (uid, name, kind, model
|
|
222
|
+
* present); the model is a whole panel, checked like one.
|
|
223
|
+
*/
|
|
224
|
+
function libraryPanelModels(dashboard: Json): Array<{ panel: Json; path: string }> {
|
|
225
|
+
const elements = dashboard.__elements;
|
|
226
|
+
if (!elements || typeof elements !== "object" || Array.isArray(elements)) return [];
|
|
227
|
+
const out: Array<{ panel: Json; path: string }> = [];
|
|
228
|
+
for (const [key, el] of Object.entries(elements as Record<string, unknown>)) {
|
|
229
|
+
if (!el || typeof el !== "object") continue;
|
|
230
|
+
const { kind, model } = el as { kind?: unknown; model?: unknown };
|
|
231
|
+
if (kind !== undefined && kind !== LIBRARY_PANEL_KIND) continue;
|
|
232
|
+
if (!model || typeof model !== "object" || Array.isArray(model)) continue;
|
|
233
|
+
out.push({ panel: model as Json, path: `/__elements/${key.replace(/~/g, "~0").replace(/\//g, "~1")}/model` });
|
|
234
|
+
}
|
|
235
|
+
return out;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/** Everything in one dashboard that the pinned schemas reject or do not know. */
|
|
102
239
|
export function validateDashboardSchema(dashboard: Json): SchemaProblem[] {
|
|
103
240
|
const out: SchemaProblem[] = [];
|
|
104
241
|
// The envelope and each panel are checked separately: the schema's
|
|
105
242
|
// `oneOf [Panel, RowPanel]` would otherwise report every panel error twice,
|
|
106
243
|
// once per branch.
|
|
107
|
-
const whole = validatorFor("dashboard", undefined, false)!;
|
|
108
244
|
const envelope = Array.isArray(dashboard.panels) ? { ...dashboard, panels: [] } : dashboard;
|
|
109
|
-
|
|
110
|
-
const panelSchema = validatorFor("dashboard", "Panel", false)!;
|
|
111
|
-
const rowSchema = validatorFor("dashboard", "RowPanel", false)!;
|
|
245
|
+
out.push(...check(envelope, "dashboard", undefined, false, ""));
|
|
112
246
|
|
|
113
247
|
for (const { panel, path } of panelsOf(dashboard)) {
|
|
114
248
|
if (!panel || typeof panel !== "object") {
|
|
115
|
-
out.push({ path, message: "must be object" });
|
|
249
|
+
out.push({ path, message: "must be object", severity: "error" });
|
|
116
250
|
continue;
|
|
117
251
|
}
|
|
118
252
|
if (panel.type === "row") {
|
|
119
253
|
const header = Array.isArray(panel.panels) ? { ...panel, panels: [] } : panel;
|
|
120
|
-
|
|
254
|
+
out.push(...check(header, "dashboard", "RowPanel", false, path));
|
|
121
255
|
continue;
|
|
122
256
|
}
|
|
123
|
-
|
|
124
|
-
const def = typeof panel.type === "string" ? panelDefinitionFor(panel.type) : undefined;
|
|
125
|
-
if (def?.schema) {
|
|
126
|
-
const options = validatorFor(def.schema, "Options", true);
|
|
127
|
-
if (options && panel.options !== undefined && !options(panel.options)) out.push(...problems(options.errors, `${path}/options`));
|
|
128
|
-
const custom = (panel.fieldConfig as { defaults?: { custom?: unknown } } | undefined)?.defaults?.custom;
|
|
129
|
-
const fc = validatorFor(def.schema, "FieldConfig", true);
|
|
130
|
-
if (fc && custom !== undefined && !fc(custom)) out.push(...problems(fc.errors, `${path}/fieldConfig/defaults/custom`));
|
|
131
|
-
}
|
|
132
|
-
const panelType = (panel.datasource as { type?: string } | undefined)?.type;
|
|
133
|
-
const targets = Array.isArray(panel.targets) ? (panel.targets as Json[]) : [];
|
|
134
|
-
targets.forEach((t, k) => {
|
|
135
|
-
const type = (t?.datasource as { type?: string } | undefined)?.type ?? panelType;
|
|
136
|
-
const qdef = type ? queryDefinitionFor(type) : undefined;
|
|
137
|
-
if (!qdef?.schema) return;
|
|
138
|
-
const v = validatorFor(qdef.schema, undefined, true);
|
|
139
|
-
if (v && !v(t)) out.push(...problems(v.errors, `${path}/targets/${k}`));
|
|
140
|
-
});
|
|
257
|
+
out.push(...checkPanel(panel, path));
|
|
141
258
|
}
|
|
259
|
+
for (const { panel, path } of libraryPanelModels(dashboard)) out.push(...checkPanel(panel, path));
|
|
142
260
|
return out;
|
|
143
261
|
}
|
|
262
|
+
|
|
263
|
+
/** The `expr` schema definition for each server-side expression type. */
|
|
264
|
+
const EXPRESSION_DEFINITIONS: Record<string, string> = {
|
|
265
|
+
math: "TypeMath",
|
|
266
|
+
reduce: "TypeReduce",
|
|
267
|
+
resample: "TypeResample",
|
|
268
|
+
classic_conditions: "TypeClassicConditions",
|
|
269
|
+
threshold: "TypeThreshold",
|
|
270
|
+
sql: "TypeSql",
|
|
271
|
+
};
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Check one server-side expression model (an alert rule's `data[].model`
|
|
275
|
+
* whose datasource is `__expr__`) against the pinned `expr` schema, as
|
|
276
|
+
* written: its `type` picks the definition, and every required field must
|
|
277
|
+
* be there, since Grafana fills none in.
|
|
278
|
+
*/
|
|
279
|
+
export function validateExpressionSchema(model: Json, prefix = ""): SchemaProblem[] {
|
|
280
|
+
const def = typeof model.type === "string" ? EXPRESSION_DEFINITIONS[model.type] : undefined;
|
|
281
|
+
if (!def) {
|
|
282
|
+
return [{ path: `${prefix}/type`, message: `is ${JSON.stringify(model.type)}, not one of ${Object.keys(EXPRESSION_DEFINITIONS).join(", ")}`, severity: "error" }];
|
|
283
|
+
}
|
|
284
|
+
return check(model, "expr", def, false, prefix);
|
|
285
|
+
}
|
package/src/schema.test.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { describe, expect, test } from "vitest";
|
|
|
2
2
|
import { readFileSync } from "fs";
|
|
3
3
|
import { dirname, join } from "path";
|
|
4
4
|
import { fileURLToPath } from "url";
|
|
5
|
-
import { GRAFANA_SCHEMA_PIN, SCHEMA_NAMES, schemaUrl } from "./pin";
|
|
5
|
+
import { GRAFANA_SCHEMA_PIN, SCHEMA_NAMES, VENDORED_SCHEMA_NAMES, schemaUrl } from "./pin";
|
|
6
6
|
import { digestMismatches, loadSchema } from "./spec/schemas";
|
|
7
7
|
import { schemaModules } from "./codegen/generate";
|
|
8
8
|
import { schemaModule, tsType } from "./codegen/schema-types";
|
|
@@ -13,7 +13,9 @@ const pkgDir = dirname(dirname(fileURLToPath(import.meta.url)));
|
|
|
13
13
|
describe("the schema pin", () => {
|
|
14
14
|
test("names a commit and a digest for every vendored schema", () => {
|
|
15
15
|
expect(GRAFANA_SCHEMA_PIN.commit).toMatch(/^[0-9a-f]{40}$/);
|
|
16
|
-
expect(Object.keys(GRAFANA_SCHEMA_PIN.files).sort()).toEqual([...
|
|
16
|
+
expect(Object.keys(GRAFANA_SCHEMA_PIN.files).sort()).toEqual([...VENDORED_SCHEMA_NAMES].sort());
|
|
17
|
+
// Types come from the classic schemas only; dashboardv2 is vendored for the importer (#2947).
|
|
18
|
+
expect(VENDORED_SCHEMA_NAMES.filter((n) => !(SCHEMA_NAMES as readonly string[]).includes(n))).toEqual(["dashboardv2"]);
|
|
17
19
|
expect(schemaUrl("stat")).toBe(`https://raw.githubusercontent.com/grafana/grafana-foundation-sdk/${GRAFANA_SCHEMA_PIN.commit}/jsonschema/stat.jsonschema.json`);
|
|
18
20
|
});
|
|
19
21
|
|
package/src/serializer.test.ts
CHANGED
|
@@ -7,7 +7,7 @@ import { load } from "js-yaml";
|
|
|
7
7
|
import type { Declarable } from "@intentius/chant/declarable";
|
|
8
8
|
import type { SerializerResult } from "@intentius/chant/serializer";
|
|
9
9
|
import { grafanaSerializer } from "./serializer";
|
|
10
|
-
import { Datasource } from "./datasource";
|
|
10
|
+
import { Datasource, ExternalDatasource } from "./datasource";
|
|
11
11
|
import { Dashboard, DashboardProvider } from "./dashboard";
|
|
12
12
|
import { StatPanel, TimeSeriesPanel } from "./panels";
|
|
13
13
|
import { PromQuery } from "./query";
|
|
@@ -150,6 +150,18 @@ describe("grafana serializer", () => {
|
|
|
150
150
|
expect(JSON.parse(out.primary).files).toEqual(Object.keys(out.files!).sort());
|
|
151
151
|
});
|
|
152
152
|
|
|
153
|
+
test("an ExternalDatasource is referenced by uid, in panels and in another datasource's settings, and not provisioned", () => {
|
|
154
|
+
const loki = new ExternalDatasource({ type: "loki", uid: "loki-prod", name: "Loki (prod)" });
|
|
155
|
+
const tempo = new Datasource({ name: "Tempo", type: "tempo", jsonData: { tracesToLogsV2: { datasourceUid: loki } } });
|
|
156
|
+
const logs = new StatPanel({ datasource: loki });
|
|
157
|
+
const out = run({ loki, tempo, d: new Dashboard({ title: "D", panels: [logs] }) });
|
|
158
|
+
expect(JSON.parse(out.files!["dashboards/d.json"]).panels[0].datasource).toEqual({ type: "loki", uid: "loki-prod" });
|
|
159
|
+
const ds = load(out.files![DATASOURCES_FILE]) as { datasources: Array<{ name: string; jsonData?: unknown }> };
|
|
160
|
+
expect(ds.datasources.map((d) => d.name)).toEqual(["Tempo"]);
|
|
161
|
+
expect(ds.datasources[0].jsonData).toEqual({ tracesToLogsV2: { datasourceUid: "loki-prod" } });
|
|
162
|
+
expect(JSON.parse(out.primary).externalDatasources).toEqual([{ type: "loki", uid: "loki-prod", name: "Loki (prod)" }]);
|
|
163
|
+
});
|
|
164
|
+
|
|
153
165
|
test("round trip: the emitted text parses back to the declared values", () => {
|
|
154
166
|
const panel = new TimeSeriesPanel({ title: "Rate", datasource: prometheus, targets: [up], interval: "30s" });
|
|
155
167
|
const out = run({ d: new Dashboard({ title: "Round trip", description: "desc", tags: ["a", "b"], panels: [panel] }) });
|
package/src/serializer.ts
CHANGED
|
@@ -9,13 +9,21 @@
|
|
|
9
9
|
* - `provisioning/datasources/chant.yaml`: every declared datasource.
|
|
10
10
|
* - `provisioning/dashboards/chant.yaml`: the provider that loads the
|
|
11
11
|
* dashboard files, a default one unless a `DashboardProvider` is declared.
|
|
12
|
+
* - `provisioning/alerting/chant.yaml`: rule groups, contact points, the
|
|
13
|
+
* notification policy tree, mute timings and templates, when the build
|
|
14
|
+
* declares any (see `alerting-build.ts`).
|
|
12
15
|
*
|
|
13
16
|
* Mount `provisioning/` at `/etc/grafana/provisioning` and `dashboards/` at
|
|
14
17
|
* the provider's path (`/var/lib/grafana/dashboards` by default).
|
|
15
18
|
*
|
|
16
|
-
* The primary output is a small JSON index of what was built.
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
+
* The primary output is a small JSON index of what was built. The dashboard
|
|
20
|
+
* JSON carries no ownership marker, because Grafana keeps who manages a
|
|
21
|
+
* dashboard outside it, in the resource's metadata on its
|
|
22
|
+
* `dashboard.grafana.app` API. For a file-provisioned dashboard that metadata
|
|
23
|
+
* is the `grafana.app/managerId` annotation Grafana writes, whose value is the
|
|
24
|
+
* provider name in `provisioning/dashboards/chant.yaml`: `chant` unless a
|
|
25
|
+
* `DashboardProvider` names another. That name is the marker chant stamps at
|
|
26
|
+
* synthesis, and ./ownership.ts reads it back.
|
|
19
27
|
*/
|
|
20
28
|
|
|
21
29
|
import type { Declarable } from "@intentius/chant/declarable";
|
package/src/skill-defs.ts
CHANGED
|
@@ -28,4 +28,38 @@ export const grafanaSkills = createSkillsLoader(import.meta.url, [
|
|
|
28
28
|
description: "Load chant-built Grafana dashboards and datasources into a running Grafana through its provisioning directories, in Docker or Kubernetes",
|
|
29
29
|
triggers: [{ type: "context" as const, value: "grafana provisioning" }],
|
|
30
30
|
},
|
|
31
|
+
{
|
|
32
|
+
file: "chant-grafana-alerting.md",
|
|
33
|
+
name: "chant-grafana-alerting",
|
|
34
|
+
description:
|
|
35
|
+
"Declare Grafana-managed alert rules, server-side expressions, contact points, notification policies and mute timings, turn an Slo into burn-rate rules, and import Grafana's alerting exports",
|
|
36
|
+
triggers: [
|
|
37
|
+
{ type: "context" as const, value: "grafana alerting" },
|
|
38
|
+
{ type: "context" as const, value: "grafana alert rule" },
|
|
39
|
+
{ type: "context" as const, value: "contact point" },
|
|
40
|
+
],
|
|
41
|
+
examples: [
|
|
42
|
+
{
|
|
43
|
+
title: "An SLO's burn-rate alerts as Grafana-managed rules",
|
|
44
|
+
output: 'export const checkoutBurn = SloAlertRules({ slo: checkout, datasource: prometheus, folder: "SLOs" });',
|
|
45
|
+
},
|
|
46
|
+
],
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
file: "chant-grafana-operations.md",
|
|
50
|
+
name: "chant-grafana-operations",
|
|
51
|
+
description:
|
|
52
|
+
"Start a Grafana project from a template, bring existing dashboards into chant with chant import, find what changed in a running Grafana with chant lifecycle diff --live, export a Grafana as TypeScript, and apply a build over the HTTP API",
|
|
53
|
+
triggers: [
|
|
54
|
+
{ type: "context" as const, value: "grafana drift" },
|
|
55
|
+
{ type: "context" as const, value: "import grafana dashboard" },
|
|
56
|
+
{ type: "context" as const, value: "grafana apply" },
|
|
57
|
+
],
|
|
58
|
+
examples: [
|
|
59
|
+
{
|
|
60
|
+
title: "See what was edited in a running Grafana since the last build",
|
|
61
|
+
output: "chant lifecycle diff staging --live",
|
|
62
|
+
},
|
|
63
|
+
],
|
|
64
|
+
},
|
|
31
65
|
]);
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
---
|
|
2
|
+
skill: chant-grafana-alerting
|
|
3
|
+
description: Declare Grafana-managed alert rules, server-side expressions, contact points, notification policies and mute timings with chant, turn an Slo into burn-rate rules, and import Grafana's alerting exports
|
|
4
|
+
user-invocable: true
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Grafana-managed alerting with chant
|
|
8
|
+
|
|
9
|
+
Everything is written to `provisioning/alerting/chant.yaml`, Grafana's alerting provisioning file. Mount `dist/grafana/provisioning` at `/etc/grafana/provisioning`.
|
|
10
|
+
|
|
11
|
+
## A rule
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
import { AlertRule, AlertRuleGroup, PromQuery, ReduceExpression, ThresholdExpression } from "@intentius/chant-lexicon-grafana";
|
|
15
|
+
|
|
16
|
+
const errors = new PromQuery({ datasource: prometheus, expr: 'sum(rate(http_requests_total{code=~"5.."}[5m]))', instant: true });
|
|
17
|
+
const last = new ReduceExpression({ expression: "A", reducer: "last" });
|
|
18
|
+
const high = new ThresholdExpression({ expression: "B", conditions: [{ evaluator: { type: "gt", params: [5] } }] });
|
|
19
|
+
const rule = new AlertRule({ title: "5xx above 5/s", uid: "checkout-5xx", data: [errors, last, high], for: "5m", labels: { severity: "page" } });
|
|
20
|
+
export const checkoutAlerts = new AlertRuleGroup({ name: "checkout", folder: "Checkout", interval: "1m", rules: [rule] });
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
- refIds follow `data` order (A, B, C); expressions name inputs by refId (`"A"`, or `$A` in a `MathExpression`). `condition` defaults to the last refId.
|
|
24
|
+
- Queries need a `Datasource`, `ExternalDatasource` or `{ type, uid }`; never a `DatasourceVariable`.
|
|
25
|
+
- Any plugin's model: `new AlertQuery({ datasource, model: { ... } })`. A typed query with its own range: `new AlertQuery({ query, relativeTimeRange: { from: "5m" } })`.
|
|
26
|
+
- Set `uid` explicitly on rules whose title may change.
|
|
27
|
+
|
|
28
|
+
## Notifications
|
|
29
|
+
|
|
30
|
+
- `ContactPoint({ name, receivers: [{ type: "slack", settings: { url: "$__env{SLACK_URL}" } }] })`. Secrets always as `$__env{...}` or `$__file{...}`; GRAF002 flags literals.
|
|
31
|
+
- `NotificationPolicy({ receiver: oncall, routes: [{ receiver: tickets, object_matchers: [["severity", "=", "ticket"]], mute_time_intervals: [weekends] }] })`. One per organisation: it replaces the whole tree.
|
|
32
|
+
- `MuteTiming({ name: "weekends", time_intervals: [{ weekdays: ["saturday", "sunday"] }] })`, `NotificationTemplate({ name, template })`.
|
|
33
|
+
- Pass entities rather than names where you can; GRAF113 checks names against what the build declares.
|
|
34
|
+
|
|
35
|
+
## An SLO's burn-rate alerts
|
|
36
|
+
|
|
37
|
+
```ts
|
|
38
|
+
export const checkoutBurn = SloAlertRules({ slo: checkout, datasource: prometheus, folder: "SLOs" });
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
One rule per window pair, reading the `Slo`'s recorded `slo:sli_error:ratio_rate<window>` series, so the `Slo`'s Prometheus rule group must be loaded. Rules carry `severity: page|ticket` for the policy tree, or pass `contactPoint`.
|
|
42
|
+
|
|
43
|
+
## Import
|
|
44
|
+
|
|
45
|
+
`chant import alert-rules.yaml` reads a provisioning file or Grafana's export (`/api/v1/provisioning/alert-rules/export?format=yaml`, and the contact-points, policies and mute-timings exports). Read every warning: guessed datasource types, `[REDACTED]` secrets turned into `$__env{...}`, and delete/reset lists that are not carried.
|
|
46
|
+
|
|
47
|
+
## Checks
|
|
48
|
+
|
|
49
|
+
GRAF108 (PromQL), GRAF111 (condition and expression refIds), GRAF112 (datasources), GRAF113 (contact points, mute timings, matchers), GRAF114 (uids, intervals as multiples of 10s, duplicates).
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
---
|
|
2
|
+
skill: chant-grafana-operations
|
|
3
|
+
description: Start a Grafana project from a template, bring existing dashboards into chant with chant import, find what changed in a running Grafana with chant lifecycle diff --live, export a Grafana as TypeScript, and apply a build over the HTTP API
|
|
4
|
+
user-invocable: true
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Operating Grafana with chant
|
|
8
|
+
|
|
9
|
+
Use this skill when a Grafana already exists: dashboards made in the UI to bring under chant, a provisioned dashboard somebody edited, or a build to push without mounting files. For writing dashboards see the chant-grafana skill; for provisioning files and ConfigMaps, chant-grafana-provisioning; for alert rules, chant-grafana-alerting.
|
|
10
|
+
|
|
11
|
+
## Start a project
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
chant init --lexicon grafana my-dashboards # a Prometheus datasource and one dashboard
|
|
15
|
+
chant init --lexicon grafana --template red my-dashboards # RedDashboard over span metrics, ExternalDatasource, Folder
|
|
16
|
+
chant init --lexicon grafana --template k8s-pods my-dashboards # pod dashboard delivered as sidecar ConfigMaps (k8s + grafana)
|
|
17
|
+
chant init --lexicon grafana --template slo my-dashboards # Slo rules, SloDashboard and SloAlertRules (prometheus + grafana)
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Every template builds clean and passes the GRAF checks as written. `k8s-pods` and `slo` ship a `chant.config.ts` naming both lexicons and a `build` script without `--lexicon`, so both outputs and both lexicons' checks run.
|
|
21
|
+
|
|
22
|
+
## Import dashboards you already have
|
|
23
|
+
|
|
24
|
+
Export the dashboard in Grafana (Export > Export as JSON, classic or V2 resource), then:
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
chant import checkout.json --output src # inside a project that lists grafana
|
|
28
|
+
chant import checkout.json --lexicon grafana --output src # anywhere else
|
|
29
|
+
chant build src --lexicon grafana -o dist/grafana/index.json
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
The importer also reads `GET /api/dashboards/uid/<uid>` responses, `dashboard.grafana.app` v1 and v2 resources, and datasource, dashboard and alerting provisioning files. Each dashboard lands in `src/<uid>/`: `dashboard.ts`, `panels.ts`, one `row-<title>.ts` per row, `variables.ts`, and `datasources.ts` with an `ExternalDatasource` per datasource named by uid. Replace an `ExternalDatasource` with a `Datasource` of the same uid to provision it from chant too.
|
|
33
|
+
|
|
34
|
+
Read every import warning. Anything the lexicon can't express is reported, never dropped. Common ones: a v2 dashboard's tabs become rows, an export made for sharing turns `${DS_*}` inputs into `DatasourceVariable`s, and two imported dashboards naming one datasource both declare it (GRAF104; delete one declaration and import it from the other file).
|
|
35
|
+
|
|
36
|
+
A dashboard inside a Kubernetes ConfigMap imports with the k8s lexicon: `chant import grafana-configmaps.yaml` gives a grafana `Dashboard` as the ConfigMap's value.
|
|
37
|
+
|
|
38
|
+
## Point environments at a Grafana
|
|
39
|
+
|
|
40
|
+
Observe, export and apply read `grafana.profiles.<env>` in `chant.config.ts`. Credentials are named by environment variable, never written in the file:
|
|
41
|
+
|
|
42
|
+
```ts
|
|
43
|
+
import type { ChantConfig } from "@intentius/chant/config";
|
|
44
|
+
import "@intentius/chant-lexicon-grafana";
|
|
45
|
+
|
|
46
|
+
export default {
|
|
47
|
+
lexicons: ["grafana"],
|
|
48
|
+
ownership: { stack: "shop", env: "prod" },
|
|
49
|
+
grafana: {
|
|
50
|
+
profiles: {
|
|
51
|
+
staging: { url: "https://grafana.staging.example.com", token: { env: "GRAFANA_STAGING_TOKEN" } },
|
|
52
|
+
prod: { url: "https://grafana.example.com", token: { env: "GRAFANA_PROD_TOKEN" }, orgId: 2 },
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
} satisfies ChantConfig;
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
With no profile, `GRAFANA_URL` plus `GRAFANA_TOKEN` (or `GRAFANA_USER` and `GRAFANA_PASSWORD`) is used. Grafana Cloud also needs `namespace: "stacks-<id>"`. A Viewer service account can observe and export. Applying needs Editor, or a custom role with `grafanaActionsFor("Apply", "Prune")`.
|
|
59
|
+
|
|
60
|
+
## Find what changed in Grafana
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
chant lifecycle diff staging --live
|
|
64
|
+
chant lifecycle diff staging --live --json # the same report as data
|
|
65
|
+
chant lifecycle diff staging --live --update-baseline # accept what it reported
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Paths are in your source's terms: `panels[0].panels[1].title` is the second panel of the first row. A value you declared that differs is drift. A value you never declared, such as a panel added in the UI, is listed as unclaimed and never proposed as a change. What the build fills in (panel ids, auto grid positions, refIds) and what Grafana adds on read are not reported. A dashboard Grafana 13 stores as v2 is read at v2 and diffed after conversion, so a tab added in the UI shows as drift in the rows.
|
|
69
|
+
|
|
70
|
+
Ownership decides what counts as chant's. A dashboard loaded by one of the project's providers (named `chant` unless you declared another `DashboardProvider`), or carrying the `app.kubernetes.io/managed-by: chant` label the applier writes, is `owned`. Anything else is `foreign`. Datasources and dashboards on Grafana 11 are `unknown`, and `--owned` leaves them out.
|
|
71
|
+
|
|
72
|
+
A `DashboardProvider` can't be observed (Grafana serves no API for provisioning files) and is reported as not observed, not missing.
|
|
73
|
+
|
|
74
|
+
## Export a Grafana as TypeScript
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
chant import --from staging --lexicon grafana --output src
|
|
78
|
+
chant import --from staging --lexicon grafana --type Grafana::Dashboard --name checkout --output src
|
|
79
|
+
chant import --from staging --lexicon grafana --owned --output src
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
It writes every dashboard and datasource in the organisation, laid out like a file import. Datasource secrets come back as `"[REDACTED]"`, each with a warning. Replace them with `$__env{NAME}` or `$__file{path}`, which GRAF002 requires.
|
|
83
|
+
|
|
84
|
+
## Apply over the HTTP API
|
|
85
|
+
|
|
86
|
+
When the build's files can't be mounted into Grafana, or you want real folders with pinned uids, apply from an Op:
|
|
87
|
+
|
|
88
|
+
```ts
|
|
89
|
+
// ops/grafana.op.ts, with "build:grafana": "chant build src --lexicon grafana -o dist/grafana.json" in package.json
|
|
90
|
+
import { Op, phase, build } from "@intentius/chant/op";
|
|
91
|
+
import { grafanaApply } from "@intentius/chant-lexicon-grafana";
|
|
92
|
+
|
|
93
|
+
export default Op({
|
|
94
|
+
name: "grafana-prod",
|
|
95
|
+
phases: [
|
|
96
|
+
phase("Build", [build(".", { script: "build:grafana" })]),
|
|
97
|
+
phase("Apply", [grafanaApply("dist/grafana.json", { environment: "prod", prune: true })]),
|
|
98
|
+
],
|
|
99
|
+
});
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Run it with `chant run grafana-prod`, or from an agent through the MCP `op-run`, `op-status` and `op-signal` tools. The applier creates folders parents first with their uids, writes library panels found in `__elements`, and sends each dashboard with the project's labels. A dashboard whose live content already matches is `unchanged` and not written; a panel title edited in the UI is put back. `prune` deletes only dashboards and folders labelled with this project's stack and env. It never touches UI-saved or file-provisioned dashboards, library panels, or anything on Grafana 11, and does nothing when no `ownership.stack` is set. Datasources still come from the provisioning file.
|
|
103
|
+
|
|
104
|
+
## A loop that keeps UI edits
|
|
105
|
+
|
|
106
|
+
1. `chant lifecycle diff prod --live` shows the edit.
|
|
107
|
+
2. Either change the source to match and rebuild, or `chant import --from prod --name <uid>` to pull the dashboard, then copy the changed panel over.
|
|
108
|
+
3. Apply, or let provisioning reload, and diff again. It should report nothing.
|
|
@@ -25,7 +25,7 @@ docker run -p 3000:3000 \
|
|
|
25
25
|
|
|
26
26
|
## Kubernetes
|
|
27
27
|
|
|
28
|
-
|
|
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. `grafanaFiles(entities)` still returns every file by path for any other layout.
|
|
29
29
|
|
|
30
30
|
## Changing where dashboards live
|
|
31
31
|
|
|
@@ -35,8 +35,8 @@ Declare a `DashboardProvider` to change the mount path, pin a folder, or allow U
|
|
|
35
35
|
export const provider = new DashboardProvider({ name: "team", path: "/dashboards", allowUiUpdates: true });
|
|
36
36
|
```
|
|
37
37
|
|
|
38
|
-
With no provider declared, chant writes one named `chant` that reads `/var/lib/grafana/dashboards` and maps subdirectories to folders.
|
|
38
|
+
With no provider declared, chant writes one named `chant` that reads `/var/lib/grafana/dashboards` and maps subdirectories to folders. A provider with `folder` set puts every dashboard in that folder and ignores the dashboards' own `folder`; GRAF109 warns. Two providers on the same or nested paths load every dashboard twice, which GRAF109 reports as an error. A dashboard `folder` of `"Platform/Kubernetes"` nests on Grafana 13.1 and later.
|
|
39
39
|
|
|
40
40
|
## Checking before Grafana does
|
|
41
41
|
|
|
42
|
-
`chant lint` and `chant build` run
|
|
42
|
+
`chant lint` and `chant build` run the GRAF1xx checks on the output, including a check against Grafana's dashboard schema at the pinned version, a PromQL syntax check on every query sent to a Prometheus, and a check that the providers put each dashboard in its declared folder, once. To check against a real Grafana, the lexicon's `import.test.ts` boots `grafana/grafana` with Docker, provisions the example and reads each dashboard back over the HTTP API.
|