@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
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Round trips of alerting provisioning files through `chant import`.
|
|
3
|
+
*
|
|
4
|
+
* Provisioning YAML -> TypeScript -> `chant build` -> provisioning YAML must
|
|
5
|
+
* give back the same file for every fixture: Grafana 12.4.11 and 13.2.2
|
|
6
|
+
* exports, and files other projects provision Grafana from (provenance in
|
|
7
|
+
* test/fixtures/alerting/README.md), and what the alerting example builds.
|
|
8
|
+
* "The same" means equal after `normalizeAlerting` once the importer's
|
|
9
|
+
* edits are applied to the source. The generated source must lint clean,
|
|
10
|
+
* and the rebuilt file must pass GRAF108 and GRAF111-GRAF114 without
|
|
11
|
+
* errors. generated-types.e2e.test.ts type-checks the generated source;
|
|
12
|
+
* alerting.e2e.test.ts provisions the rebuilt exports into Grafana.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { describe, expect, test } from "vitest";
|
|
16
|
+
import { join } from "path";
|
|
17
|
+
import { load } from "js-yaml";
|
|
18
|
+
import { build } from "@intentius/chant/build";
|
|
19
|
+
import type { SerializerResult } from "@intentius/chant/serializer";
|
|
20
|
+
import { lintCommand } from "@intentius/chant/cli/commands/lint";
|
|
21
|
+
import { importCommand } from "@intentius/chant/cli/commands/import";
|
|
22
|
+
import { grafanaSerializer } from "../serializer";
|
|
23
|
+
import { ALERTING_FILE } from "../alerting-build";
|
|
24
|
+
import { buildGrafana } from "../build";
|
|
25
|
+
import { validateGrafanaOutput, type GrafanaIssue } from "../validate-output";
|
|
26
|
+
import { GrafanaParser, type AlertingResourceMetadata } from "./parser";
|
|
27
|
+
import { GrafanaGenerator } from "./generator";
|
|
28
|
+
import { applyEdits } from "./edits";
|
|
29
|
+
import { normalizeAlerting } from "./normalize-alerting";
|
|
30
|
+
import { ALERTING, exampleOutputs, fixturesDir, projectDir, read, removeDir, repoRoot, writeFiles } from "./testdata/fixtures";
|
|
31
|
+
|
|
32
|
+
type Json = Record<string, unknown>;
|
|
33
|
+
|
|
34
|
+
interface Imported {
|
|
35
|
+
source: string;
|
|
36
|
+
warnings: string[];
|
|
37
|
+
expected: Json;
|
|
38
|
+
rebuilt: Json;
|
|
39
|
+
issues: GrafanaIssue[];
|
|
40
|
+
lint: { errorCount: number; warningCount: number; output: string };
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** Several files -> IR -> TypeScript in one project -> `chant build`, `chant lint`, and the checks. */
|
|
44
|
+
async function importAndBuild(contents: string[]): Promise<Imported> {
|
|
45
|
+
const dir = projectDir();
|
|
46
|
+
try {
|
|
47
|
+
const srcDir = join(dir, "src");
|
|
48
|
+
const warnings: string[] = [];
|
|
49
|
+
const sources: string[] = [];
|
|
50
|
+
const expected: Json = { apiVersion: 1 };
|
|
51
|
+
contents.forEach((content, i) => {
|
|
52
|
+
const ir = new GrafanaParser().parse(content);
|
|
53
|
+
warnings.push(...(ir.warnings ?? []));
|
|
54
|
+
const generated = new GrafanaGenerator().generate(ir).map((f) => ({ ...f, path: contents.length > 1 ? `f${i}/${f.path}` : f.path }));
|
|
55
|
+
writeFiles(srcDir, generated);
|
|
56
|
+
sources.push(...generated.map((f) => `// ${f.path}\n${f.content}`));
|
|
57
|
+
const meta = ir.resources[0]!.metadata as unknown as AlertingResourceMetadata;
|
|
58
|
+
for (const [k, v] of Object.entries(applyEdits(meta.source, meta.edits))) {
|
|
59
|
+
if (Array.isArray(v)) expected[k] = [...((expected[k] as unknown[]) ?? []), ...v];
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
const result = await build(srcDir, [grafanaSerializer]);
|
|
63
|
+
expect(result.errors).toEqual([]);
|
|
64
|
+
const text = (result.outputs.get("grafana") as SerializerResult | undefined)?.files?.[ALERTING_FILE];
|
|
65
|
+
const built = buildGrafana(new Map([...result.entities].filter(([, e]) => e.lexicon === "grafana")));
|
|
66
|
+
const issues = validateGrafanaOutput({
|
|
67
|
+
dashboards: [],
|
|
68
|
+
datasources: built.datasources,
|
|
69
|
+
externalDatasources: built.externalDatasources,
|
|
70
|
+
alerting: built.alerting ? [{ json: built.alerting as unknown as Json }] : [],
|
|
71
|
+
});
|
|
72
|
+
const lint = await lintCommand({ path: srcDir, format: "stylish" });
|
|
73
|
+
return {
|
|
74
|
+
source: sources.join("\n"),
|
|
75
|
+
warnings,
|
|
76
|
+
expected,
|
|
77
|
+
rebuilt: text === undefined ? { apiVersion: 1 } : (load(text) as Json),
|
|
78
|
+
issues,
|
|
79
|
+
lint: { errorCount: lint.errorCount, warningCount: lint.warningCount, output: lint.output },
|
|
80
|
+
};
|
|
81
|
+
} finally {
|
|
82
|
+
removeDir(dir);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
async function expectRoundTrip(...contents: string[]): Promise<Imported> {
|
|
87
|
+
const out = await importAndBuild(contents);
|
|
88
|
+
expect(normalizeAlerting(out.rebuilt)).toEqual(normalizeAlerting(out.expected));
|
|
89
|
+
expect(out.lint.errorCount + out.lint.warningCount, out.lint.output).toBe(0);
|
|
90
|
+
expect(out.issues.filter((i) => i.severity === "error")).toEqual([]);
|
|
91
|
+
return out;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
describe("alerting provisioning YAML -> TypeScript -> alerting provisioning YAML", () => {
|
|
95
|
+
for (const file of ALERTING) {
|
|
96
|
+
test(file, async () => {
|
|
97
|
+
await expectRoundTrip(read(file));
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
test("each Grafana version's exports, imported into one project, reference each other and pass every check", async () => {
|
|
102
|
+
for (const v of ["grafana-12.4.11", "grafana-13.2.2"]) {
|
|
103
|
+
const files = ALERTING.filter((f) => f.startsWith(`alerting/${v}/`)).map((f) => read(f));
|
|
104
|
+
const out = await expectRoundTrip(...files);
|
|
105
|
+
// With the contact points and mute timings declared, GRAF113 checks every reference and finds them all.
|
|
106
|
+
expect(out.issues.filter((i) => i.code === "GRAF113")).toEqual([]);
|
|
107
|
+
expect(out.issues.filter((i) => i.code === "GRAF112")).toEqual([]);
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
test("a project's files, imported together, pass every check", async () => {
|
|
112
|
+
const marin = ALERTING.filter((f) => f.includes("/marin.")).map((f) => read(f));
|
|
113
|
+
const out = await expectRoundTrip(...marin);
|
|
114
|
+
expect(out.issues.filter((i) => i.code === "GRAF113")).toEqual([]);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
test("the typed classes carry each server-side expression, and a datasource query keeps its model", async () => {
|
|
118
|
+
const out = await importAndBuild([read("alerting/grafana-13.2.2/alert-rules.yaml")]);
|
|
119
|
+
for (const cls of ["ReduceExpression", "ThresholdExpression", "ResampleExpression", "MathExpression", "ClassicConditionsExpression"]) {
|
|
120
|
+
expect(out.source).toContain(`new ${cls}(`);
|
|
121
|
+
}
|
|
122
|
+
expect(out.source).toContain('const prom = new ExternalDatasource({ type: "prometheus", uid: "prom" });');
|
|
123
|
+
expect(out.source).toContain("new AlertQuery({ datasource: prom, model:");
|
|
124
|
+
// The recovery threshold is carried; the editor's operator/query/reducer on a threshold are not needed.
|
|
125
|
+
expect(out.source).toContain('unloadEvaluator: { params: [0.03], type: "lt" }');
|
|
126
|
+
expect(out.warnings).toEqual([]);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
test("what cannot be carried, or had to be guessed, is a warning", async () => {
|
|
130
|
+
const proto = await importAndBuild([read("alerting/community/proto-fleet.proto-fleet-rules.yaml")]);
|
|
131
|
+
expect(proto.warnings).toContainEqual(expect.stringContaining('the plugin type of "protofleet-timescaledb"'));
|
|
132
|
+
expect(proto.warnings).toContainEqual(expect.stringContaining("2 rule deletions (deleteRules)"));
|
|
133
|
+
expect(proto.source).toContain('datasource: "protofleet-timescaledb"');
|
|
134
|
+
|
|
135
|
+
const eth = await importAndBuild([read("alerting/community/eth-docker.disk_space.yml")]);
|
|
136
|
+
expect(eth.warnings).toContainEqual(expect.stringContaining('"PBFA97CFB590B2093"; the queries look like PromQL'));
|
|
137
|
+
|
|
138
|
+
const cps = await importAndBuild([read("alerting/grafana-13.2.2/contact-points.yaml")]);
|
|
139
|
+
expect(cps.warnings).toEqual([expect.stringContaining("url as $__env{ONCALL_SLACK_URL} is read from the environment")]);
|
|
140
|
+
expect(cps.rebuilt).toMatchObject({ contactPoints: expect.arrayContaining([expect.objectContaining({ name: "oncall" })]) });
|
|
141
|
+
expect(JSON.stringify(cps.rebuilt)).not.toContain("[REDACTED]");
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
test("the alerting example's file round-trips", async () => {
|
|
145
|
+
const [, text] = (await exampleOutputs()).find(([f]) => f === `alerting/${ALERTING_FILE}`)!;
|
|
146
|
+
await expectRoundTrip(text);
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
test("chant import detects the file as grafana's, and a Prometheus rule file as prometheus's", async () => {
|
|
150
|
+
const dir = projectDir();
|
|
151
|
+
try {
|
|
152
|
+
const alerting = await importCommand({ templatePath: join(fixturesDir, "alerting", "grafana-13.2.2", "policies.yaml"), output: join(dir, "src"), force: true });
|
|
153
|
+
expect(alerting.lexicon).toBe("grafana");
|
|
154
|
+
expect(alerting.generatedFiles).toEqual(["notifications.ts"]);
|
|
155
|
+
const rules = await importCommand({ templatePath: join(repoRoot, "lexicons/prometheus/src/import/testdata/rules-full.yml"), output: join(dir, "rules"), force: true });
|
|
156
|
+
expect(rules.lexicon).toBe("prometheus");
|
|
157
|
+
} finally {
|
|
158
|
+
removeDir(dir);
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
});
|
|
@@ -0,0 +1,500 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* An alerting provisioning file -> a `Plan`, for `chant import`.
|
|
3
|
+
*
|
|
4
|
+
* Accepts what Grafana's file provisioner reads and what
|
|
5
|
+
* `GET /api/v1/provisioning/*\/export` writes (the same format at v12.4.11
|
|
6
|
+
* and v13.2.2): rule groups, contact points, notification policies, mute
|
|
7
|
+
* timings and templates, in one file or split across several.
|
|
8
|
+
*
|
|
9
|
+
* - A rule group becomes an `AlertRuleGroup`, each rule an `AlertRule`,
|
|
10
|
+
* each entry of a rule's `data` a declaration of its own: a server-side
|
|
11
|
+
* expression as its typed class (`ReduceExpression`, `ThresholdExpression`,
|
|
12
|
+
* ...), and a datasource query as an `AlertQuery` holding its model as it
|
|
13
|
+
* is, so nothing a plugin stores is lost.
|
|
14
|
+
* - A datasource uid a query names becomes an `ExternalDatasource` when the
|
|
15
|
+
* file says its plugin type (`model.datasource.type`), so the checks can
|
|
16
|
+
* see it (GRAF108 parses Prometheus queries). When the type has to be
|
|
17
|
+
* inferred from the model, the import says so; when it cannot be told,
|
|
18
|
+
* the query keeps the bare uid and the import says that too.
|
|
19
|
+
* - A contact point, policy, mute timing or template becomes its class;
|
|
20
|
+
* a receiver or timing named in the same file is referenced by variable.
|
|
21
|
+
* - `deleteRules`, `deleteContactPoints`, `resetPolicies`, `deleteMuteTimes`
|
|
22
|
+
* and `deleteTemplates` are not carried, with a warning.
|
|
23
|
+
*
|
|
24
|
+
* Grafana's editor writes keys into expression models that the expression
|
|
25
|
+
* does not read (a `conditions` block on reduce and math, `operator`,
|
|
26
|
+
* `query`, `reducer` and `type` in a threshold's conditions, `type` and
|
|
27
|
+
* `reducer.params` in classic conditions). They are left out as edits without a warning:
|
|
28
|
+
* Grafana evaluates the rule the same without them.
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
import { pointer, type ImportEdit } from "./edits";
|
|
32
|
+
import { NO_PROP, Report } from "./report";
|
|
33
|
+
import { declRef, type Declaration, type DeclRef, type Plan } from "./model";
|
|
34
|
+
import { isObject, refIdAt } from "./normalize";
|
|
35
|
+
import { DEFAULT_RELATIVE_TIME_RANGE } from "../alerting-build";
|
|
36
|
+
import { EXPRESSION_DATASOURCE_UID, type ExpressionKind } from "../alerting";
|
|
37
|
+
import { validateExpressionSchema } from "../schema-validate";
|
|
38
|
+
import { REDACTED, secretSettings } from "../contact-point-secrets";
|
|
39
|
+
|
|
40
|
+
type Json = Record<string, unknown>;
|
|
41
|
+
|
|
42
|
+
const EXPRESSION_CLASS: Record<ExpressionKind, string> = {
|
|
43
|
+
reduce: "ReduceExpression",
|
|
44
|
+
math: "MathExpression",
|
|
45
|
+
threshold: "ThresholdExpression",
|
|
46
|
+
resample: "ResampleExpression",
|
|
47
|
+
classic_conditions: "ClassicConditionsExpression",
|
|
48
|
+
sql: "SqlExpression",
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
/** Keys every expression model may hold (the pinned `expr` schema's common fields). */
|
|
52
|
+
const EXPRESSION_COMMON = ["hide", "intervalMs", "maxDataPoints", "queryType", "resultAssertions", "timeRange"];
|
|
53
|
+
|
|
54
|
+
/** Keys each expression type reads, from the pinned `expr` schema. */
|
|
55
|
+
const EXPRESSION_KEYS: Record<ExpressionKind, string[]> = {
|
|
56
|
+
math: ["expression"],
|
|
57
|
+
reduce: ["expression", "reducer", "settings"],
|
|
58
|
+
resample: ["expression", "window", "downsampler", "upsampler"],
|
|
59
|
+
classic_conditions: ["conditions"],
|
|
60
|
+
threshold: ["expression", "conditions"],
|
|
61
|
+
sql: ["expression", "format"],
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Keys of a condition each command reads: the threshold command
|
|
66
|
+
* (`pkg/expr/threshold.go`) and classic conditions (`ConditionJSON` in
|
|
67
|
+
* `pkg/expr/classic`). The editor writes more (`operator`, `query` and
|
|
68
|
+
* `reducer` on a threshold, `type: query` on both).
|
|
69
|
+
*/
|
|
70
|
+
const CONDITION_KEYS: Partial<Record<ExpressionKind, string[]>> = {
|
|
71
|
+
threshold: ["evaluator", "unloadEvaluator", "loadedDimensions", "loadedFingerprints"],
|
|
72
|
+
classic_conditions: ["evaluator", "operator", "query", "reducer"],
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
/** A classic condition's reducer is read by `type` alone; the editor also writes an empty `params` (`ConditionReducerJSON`). */
|
|
76
|
+
function classicReducerParams(kind: ExpressionKind, c: Json): boolean {
|
|
77
|
+
return kind === "classic_conditions" && isObject(c.reducer) && "params" in c.reducer;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function conditionsKept(kind: ExpressionKind, conditions: unknown[]): unknown[] {
|
|
81
|
+
const keys = CONDITION_KEYS[kind]!;
|
|
82
|
+
return conditions.map((c) => {
|
|
83
|
+
if (!isObject(c)) return c;
|
|
84
|
+
const kept = Object.fromEntries(Object.entries(c).filter(([k]) => keys.includes(k)));
|
|
85
|
+
if (classicReducerParams(kind, c)) {
|
|
86
|
+
const reducer = { ...(c.reducer as Json) };
|
|
87
|
+
delete reducer.params;
|
|
88
|
+
kept.reducer = reducer;
|
|
89
|
+
}
|
|
90
|
+
return kept;
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const RULE_FIELDS = [
|
|
95
|
+
"uid",
|
|
96
|
+
"title",
|
|
97
|
+
"condition",
|
|
98
|
+
"data",
|
|
99
|
+
"dashboardUid",
|
|
100
|
+
"dasboardUid",
|
|
101
|
+
"panelId",
|
|
102
|
+
"noDataState",
|
|
103
|
+
"execErrState",
|
|
104
|
+
"for",
|
|
105
|
+
"keepFiringFor",
|
|
106
|
+
"missing_series_evals_to_resolve",
|
|
107
|
+
"annotations",
|
|
108
|
+
"labels",
|
|
109
|
+
"isPaused",
|
|
110
|
+
"notification_settings",
|
|
111
|
+
"record",
|
|
112
|
+
];
|
|
113
|
+
|
|
114
|
+
const GROUP_FIELDS = ["orgId", "name", "folder", "interval", "rules"];
|
|
115
|
+
const NOTIFICATION_SETTINGS_FIELDS = ["receiver", "group_by", "group_wait", "group_interval", "repeat_interval", "mute_time_intervals", "active_time_intervals"];
|
|
116
|
+
const ROUTE_FIELDS = [
|
|
117
|
+
"receiver",
|
|
118
|
+
"group_by",
|
|
119
|
+
"object_matchers",
|
|
120
|
+
"matchers",
|
|
121
|
+
"match",
|
|
122
|
+
"match_re",
|
|
123
|
+
"continue",
|
|
124
|
+
"group_wait",
|
|
125
|
+
"group_interval",
|
|
126
|
+
"repeat_interval",
|
|
127
|
+
"mute_time_intervals",
|
|
128
|
+
"active_time_intervals",
|
|
129
|
+
"routes",
|
|
130
|
+
];
|
|
131
|
+
const DELETIONS: Record<string, string> = {
|
|
132
|
+
deleteRules: "rule deletions",
|
|
133
|
+
deleteContactPoints: "contact point deletions",
|
|
134
|
+
resetPolicies: "policy resets",
|
|
135
|
+
deleteMuteTimes: "mute timing deletions",
|
|
136
|
+
deleteTemplates: "template deletions",
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
function isExpressionKind(v: unknown): v is ExpressionKind {
|
|
140
|
+
return typeof v === "string" && Object.prototype.hasOwnProperty.call(EXPRESSION_CLASS, v);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/** A datasource's plugin type as far as one query shows it: stated in the model, or inferred from its keys. */
|
|
144
|
+
function pluginTypeOf(model: Json, queryType: unknown): { type: string; inferred: boolean } | undefined {
|
|
145
|
+
const ds = model.datasource;
|
|
146
|
+
if (isObject(ds) && typeof ds.type === "string" && ds.type !== "" && ds.type !== EXPRESSION_DATASOURCE_UID) return { type: ds.type, inferred: false };
|
|
147
|
+
// Prometheus: PromQL in `expr` with the editor's `instant`/`range` switches, and no Loki `queryType`.
|
|
148
|
+
if (typeof model.expr === "string" && (typeof model.instant === "boolean" || typeof model.range === "boolean") && model.queryType === undefined && !queryType) {
|
|
149
|
+
return { type: "prometheus", inferred: true };
|
|
150
|
+
}
|
|
151
|
+
return undefined;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
class AlertingConverter {
|
|
155
|
+
readonly declarations: Declaration[] = [];
|
|
156
|
+
private readonly datasources = new Map<string, { id: string; type: string; inferred: boolean }>();
|
|
157
|
+
private readonly untyped = new Set<string>();
|
|
158
|
+
private readonly contactPoints = new Map<string, string>();
|
|
159
|
+
private readonly timings = new Map<string, string>();
|
|
160
|
+
|
|
161
|
+
constructor(
|
|
162
|
+
private readonly doc: Json,
|
|
163
|
+
private readonly report: Report,
|
|
164
|
+
) {}
|
|
165
|
+
|
|
166
|
+
private receiverRef(name: unknown): unknown {
|
|
167
|
+
return typeof name === "string" && this.contactPoints.has(name) ? declRef(this.contactPoints.get(name)!) : name;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
private timingRefs(list: unknown): unknown {
|
|
171
|
+
return Array.isArray(list) ? list.map((n) => (typeof n === "string" && this.timings.has(n) ? declRef(this.timings.get(n)!) : n)) : list;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/** Scan every query for the datasources it names, and declare the ones whose type is known. */
|
|
175
|
+
private scanDatasources(groups: unknown[]): void {
|
|
176
|
+
const found = new Map<string, { type?: string; inferred: boolean }>();
|
|
177
|
+
for (const g of groups) {
|
|
178
|
+
for (const r of isObject(g) && Array.isArray(g.rules) ? g.rules : []) {
|
|
179
|
+
for (const q of isObject(r) && Array.isArray(r.data) ? r.data : []) {
|
|
180
|
+
if (!isObject(q) || typeof q.datasourceUid !== "string" || q.datasourceUid === EXPRESSION_DATASOURCE_UID) continue;
|
|
181
|
+
const t = pluginTypeOf(isObject(q.model) ? q.model : {}, q.queryType);
|
|
182
|
+
const prev = found.get(q.datasourceUid);
|
|
183
|
+
if (!prev?.type || (prev.inferred && t && !t.inferred)) found.set(q.datasourceUid, { type: t?.type, inferred: t?.inferred ?? false });
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
for (const [uid, t] of [...found].sort(([a], [b]) => a.localeCompare(b))) {
|
|
188
|
+
if (!t.type) {
|
|
189
|
+
this.untyped.add(uid);
|
|
190
|
+
continue;
|
|
191
|
+
}
|
|
192
|
+
const id = `datasource:${uid}`;
|
|
193
|
+
this.datasources.set(uid, { id, type: t.type, inferred: t.inferred });
|
|
194
|
+
this.declarations.push({ id, kind: "new", className: "ExternalDatasource", props: { type: t.type, uid }, name: uid, module: "datasources" });
|
|
195
|
+
}
|
|
196
|
+
const inferred = [...this.datasources].filter(([, d]) => d.inferred).map(([uid]) => `"${uid}"`);
|
|
197
|
+
if (inferred.length > 0) {
|
|
198
|
+
this.report.warn(
|
|
199
|
+
`datasources: the file does not say the plugin type of ${inferred.join(", ")}; the queries look like PromQL, so ${inferred.length === 1 ? "it is" : "they are"} declared as a prometheus ExternalDatasource. Change the type if that is wrong.`,
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
if (this.untyped.size > 0) {
|
|
203
|
+
const uids = [...this.untyped].map((u) => `"${u}"`).join(", ");
|
|
204
|
+
this.report.warn(
|
|
205
|
+
`datasources: the file does not say the plugin type of ${uids}, so the queries name ${this.untyped.size === 1 ? "it" : "them"} by uid alone and the checks cannot see what ${this.untyped.size === 1 ? "it is" : "they are"}. Declare an ExternalDatasource with its type and pass it as the query's datasource.`,
|
|
206
|
+
);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
private templates(list: unknown[]): void {
|
|
211
|
+
list.forEach((t, i) => {
|
|
212
|
+
if (!isObject(t) || typeof t.name !== "string" || typeof t.template !== "string") {
|
|
213
|
+
this.report.drop(pointer("templates", i), "templates", `entry ${i}`, "(it has no name or template)");
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
const props: Json = { name: t.name, ...(t.orgId !== undefined ? { orgId: t.orgId } : {}), template: t.template };
|
|
217
|
+
for (const k of Object.keys(t)) if (!["name", "orgId", "template"].includes(k)) this.report.drop(pointer("templates", i, k), `template "${t.name}"`, k, NO_PROP);
|
|
218
|
+
this.declarations.push({ id: `template:${i}`, kind: "new", className: "NotificationTemplate", props, name: `${t.name} template`, module: "notifications" });
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
private muteTimes(list: unknown[]): void {
|
|
223
|
+
list.forEach((t, i) => {
|
|
224
|
+
if (!isObject(t) || typeof t.name !== "string") {
|
|
225
|
+
this.report.drop(pointer("muteTimes", i), "muteTimes", `entry ${i}`, "(it has no name)");
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
const id = `mute:${i}`;
|
|
229
|
+
this.timings.set(t.name, id);
|
|
230
|
+
const props: Json = { name: t.name, ...(t.orgId !== undefined ? { orgId: t.orgId } : {}), time_intervals: Array.isArray(t.time_intervals) ? t.time_intervals : [] };
|
|
231
|
+
for (const k of Object.keys(t)) if (!["name", "orgId", "time_intervals"].includes(k)) this.report.drop(pointer("muteTimes", i, k), `mute timing "${t.name}"`, k, NO_PROP);
|
|
232
|
+
this.declarations.push({ id, kind: "new", className: "MuteTiming", props, name: `${t.name} mute timing`, module: "notifications" });
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
private contactPointList(list: unknown[]): void {
|
|
237
|
+
list.forEach((cp, i) => {
|
|
238
|
+
if (!isObject(cp) || typeof cp.name !== "string") {
|
|
239
|
+
this.report.drop(pointer("contactPoints", i), "contactPoints", `entry ${i}`, "(it has no name)");
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
const id = `contact-point:${i}`;
|
|
243
|
+
this.contactPoints.set(cp.name, id);
|
|
244
|
+
const subject = `contact point "${cp.name}"`;
|
|
245
|
+
const receivers = (Array.isArray(cp.receivers) ? cp.receivers : []).flatMap((r, j) => {
|
|
246
|
+
if (!isObject(r) || typeof r.type !== "string") {
|
|
247
|
+
this.report.drop(pointer("contactPoints", i, "receivers", j), subject, `receivers[${j}]`, "(it has no type)");
|
|
248
|
+
return [];
|
|
249
|
+
}
|
|
250
|
+
const out: Json = {};
|
|
251
|
+
for (const k of ["uid", "type", "settings", "disableResolveMessage"]) if (r[k] !== undefined && r[k] !== null) out[k] = r[k];
|
|
252
|
+
if (out.settings === undefined) out.settings = {};
|
|
253
|
+
if (isObject(out.settings)) out.settings = this.redacted(out.settings, r.type as string, cp.name as string, ["contactPoints", i, "receivers", j, "settings"]);
|
|
254
|
+
for (const k of Object.keys(r)) {
|
|
255
|
+
if (!["uid", "type", "settings", "disableResolveMessage"].includes(k)) this.report.drop(pointer("contactPoints", i, "receivers", j, k), subject, k, NO_PROP);
|
|
256
|
+
}
|
|
257
|
+
return [out];
|
|
258
|
+
});
|
|
259
|
+
for (const k of Object.keys(cp)) if (!["name", "orgId", "receivers"].includes(k)) this.report.drop(pointer("contactPoints", i, k), subject, k, NO_PROP);
|
|
260
|
+
const props: Json = { name: cp.name, ...(cp.orgId !== undefined ? { orgId: cp.orgId } : {}), receivers };
|
|
261
|
+
this.declarations.push({ id, kind: "new", className: "ContactPoint", props, name: `${cp.name} contact point`, module: "notifications" });
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* An export made without secrets writes `[REDACTED]` for each; provisioning
|
|
267
|
+
* that would send the literal text. Each becomes `$__env{NAME}`, named from
|
|
268
|
+
* the contact point, integration and setting, with a warning.
|
|
269
|
+
*/
|
|
270
|
+
private redacted(settings: Json, type: string, name: string, path: Array<string | number>): Json {
|
|
271
|
+
const out = structuredClone(settings);
|
|
272
|
+
const names: string[] = [];
|
|
273
|
+
for (const [key, value] of secretSettings(type, settings)) {
|
|
274
|
+
if (value !== REDACTED) continue;
|
|
275
|
+
const env = `${name} ${type} ${key}`.toUpperCase().replace(/[^A-Z0-9]+/g, "_").replace(/^_|_$/g, "");
|
|
276
|
+
const segments = key.split(".");
|
|
277
|
+
let node = out;
|
|
278
|
+
for (const seg of segments.slice(0, -1)) node = node[seg] as Json;
|
|
279
|
+
node[segments[segments.length - 1]] = `$__env{${env}}`;
|
|
280
|
+
this.report.edit({ op: "replace", path: pointer(...path, ...segments), value: `$__env{${env}}` });
|
|
281
|
+
names.push(`${key} as $__env{${env}}`);
|
|
282
|
+
}
|
|
283
|
+
if (names.length > 0) {
|
|
284
|
+
this.report.warn(`contact point "${name}" (${type}): the file was exported without secrets, so ${names.join(" and ")} ${names.length === 1 ? "is" : "are"} read from the environment; set ${names.length === 1 ? "it" : "them"} where Grafana runs`);
|
|
285
|
+
}
|
|
286
|
+
return out;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
private route(r: Json, path: Array<string | number>, subject: string): Json {
|
|
290
|
+
const out: Json = {};
|
|
291
|
+
for (const k of ROUTE_FIELDS) {
|
|
292
|
+
const v = r[k];
|
|
293
|
+
if (v === undefined || v === null) continue;
|
|
294
|
+
if (k === "receiver") out.receiver = this.receiverRef(v);
|
|
295
|
+
else if (k === "mute_time_intervals" || k === "active_time_intervals") out[k] = this.timingRefs(v);
|
|
296
|
+
else if (k === "routes" && Array.isArray(v)) out.routes = v.map((c, i) => (isObject(c) ? this.route(c, [...path, "routes", i], subject) : c));
|
|
297
|
+
else out[k] = v;
|
|
298
|
+
}
|
|
299
|
+
for (const k of Object.keys(r)) if (!ROUTE_FIELDS.includes(k) && k !== "orgId") this.report.drop(pointer(...path, k), subject, k, NO_PROP);
|
|
300
|
+
return out;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
private policies(list: unknown[]): void {
|
|
304
|
+
list.forEach((p, i) => {
|
|
305
|
+
if (!isObject(p) || typeof p.receiver !== "string") {
|
|
306
|
+
this.report.drop(pointer("policies", i), "policies", `entry ${i}`, "(the root policy has no receiver)");
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
const subject = "the notification policy tree";
|
|
310
|
+
const route = this.route(p, ["policies", i], subject);
|
|
311
|
+
for (const k of ["object_matchers", "matchers", "match", "match_re", "continue"]) {
|
|
312
|
+
if (route[k] !== undefined) {
|
|
313
|
+
this.report.drop(pointer("policies", i, k), subject, k, "(the root policy matches every alert)");
|
|
314
|
+
delete route[k];
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
const props: Json = { ...(p.orgId !== undefined ? { orgId: p.orgId } : {}), ...route };
|
|
318
|
+
const org = typeof p.orgId === "number" && p.orgId !== 1 ? ` org ${p.orgId}` : "";
|
|
319
|
+
this.declarations.push({ id: `policy:${i}`, kind: "new", className: "NotificationPolicy", props, name: `notification policy${org}`, module: "notifications" });
|
|
320
|
+
});
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/** True when the model, less the keys the editor writes and the expression ignores, is valid for its typed class. */
|
|
324
|
+
private fitsSchema(model: Json, kind: ExpressionKind, vestigial: string[]): boolean {
|
|
325
|
+
const m: Json = { ...model };
|
|
326
|
+
for (const k of [...vestigial, "datasource", "refId"]) delete m[k];
|
|
327
|
+
if (CONDITION_KEYS[kind] && Array.isArray(m.conditions)) m.conditions = conditionsKept(kind, m.conditions);
|
|
328
|
+
return validateExpressionSchema(m).length === 0;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/** One entry of a rule's `data`: its declaration's class and props. */
|
|
332
|
+
private query(q: Json, path: Array<string | number>, index: number, subject: string): { className: string; props: Json } {
|
|
333
|
+
const model = isObject(q.model) ? q.model : {};
|
|
334
|
+
const refId = typeof q.refId === "string" ? q.refId : refIdAt(index);
|
|
335
|
+
const range = isObject(q.relativeTimeRange) ? q.relativeTimeRange : undefined;
|
|
336
|
+
for (const k of Object.keys(q)) {
|
|
337
|
+
if (!["refId", "queryType", "relativeTimeRange", "datasourceUid", "model"].includes(k)) this.report.drop(pointer(...path, k), subject, k, NO_PROP);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
if (q.datasourceUid === EXPRESSION_DATASOURCE_UID && isExpressionKind(model.type) && (q.queryType === undefined || q.queryType === "")) {
|
|
341
|
+
const kind = model.type;
|
|
342
|
+
const allowed = new Set([...EXPRESSION_COMMON, ...EXPRESSION_KEYS[kind], "type", "datasource", "refId"]);
|
|
343
|
+
const vestigial = kind !== "classic_conditions" && kind !== "threshold" ? ["conditions"] : [];
|
|
344
|
+
const unknown = Object.keys(model).filter((k) => !allowed.has(k) && !vestigial.includes(k));
|
|
345
|
+
const ds = model.datasource;
|
|
346
|
+
const dsIsExpr = ds === undefined || (isObject(ds) && ds.uid === EXPRESSION_DATASOURCE_UID && Object.keys(ds).every((k) => k === "type" || k === "uid"));
|
|
347
|
+
if (unknown.length === 0 && dsIsExpr && this.fitsSchema(model, kind, vestigial)) {
|
|
348
|
+
const props: Json = {};
|
|
349
|
+
if (refId !== refIdAt(index)) props.refId = refId;
|
|
350
|
+
if (range && (range.from !== 0 || (range.to !== undefined && range.to !== 0))) props.relativeTimeRange = range;
|
|
351
|
+
for (const [k, v] of Object.entries(model)) {
|
|
352
|
+
if (k === "type" || k === "datasource" || k === "refId") continue;
|
|
353
|
+
if (vestigial.includes(k)) {
|
|
354
|
+
this.report.drop(pointer(...path, "model", k), subject, k);
|
|
355
|
+
continue;
|
|
356
|
+
}
|
|
357
|
+
if (k === "conditions" && CONDITION_KEYS[kind] && Array.isArray(v)) {
|
|
358
|
+
v.forEach((c, ci) => {
|
|
359
|
+
if (!isObject(c)) return;
|
|
360
|
+
for (const ck of Object.keys(c)) if (!CONDITION_KEYS[kind]!.includes(ck)) this.report.drop(pointer(...path, "model", "conditions", ci, ck), subject, ck);
|
|
361
|
+
if (classicReducerParams(kind, c)) this.report.drop(pointer(...path, "model", "conditions", ci, "reducer", "params"), subject, "reducer.params");
|
|
362
|
+
});
|
|
363
|
+
props.conditions = conditionsKept(kind, v);
|
|
364
|
+
continue;
|
|
365
|
+
}
|
|
366
|
+
props[k] = v;
|
|
367
|
+
}
|
|
368
|
+
return { className: EXPRESSION_CLASS[kind], props };
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
// A datasource query, or an expression the typed classes cannot hold: its model as it is.
|
|
373
|
+
const uid = typeof q.datasourceUid === "string" ? q.datasourceUid : undefined;
|
|
374
|
+
const props: Json = {};
|
|
375
|
+
if (uid !== undefined) props.datasource = this.datasources.has(uid) ? declRef(this.datasources.get(uid)!.id) : uid;
|
|
376
|
+
props.model = model;
|
|
377
|
+
if (refId !== refIdAt(index)) props.refId = refId;
|
|
378
|
+
if (typeof q.queryType === "string" && q.queryType !== "") props.queryType = q.queryType;
|
|
379
|
+
const from = range?.from ?? 0;
|
|
380
|
+
const to = range?.to ?? 0;
|
|
381
|
+
if (from !== DEFAULT_RELATIVE_TIME_RANGE.from || to !== DEFAULT_RELATIVE_TIME_RANGE.to) props.relativeTimeRange = to === 0 ? { from } : { from, to };
|
|
382
|
+
return { className: "AlertQuery", props };
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
private rule(r: Json, gi: number, ri: number, unit: string): DeclRef | undefined {
|
|
386
|
+
const path: Array<string | number> = ["groups", gi, "rules", ri];
|
|
387
|
+
if (typeof r.title !== "string" || !Array.isArray(r.data)) {
|
|
388
|
+
this.report.drop(pointer(...path), `group ${gi}`, `rules[${ri}]`, "(it has no title or data)");
|
|
389
|
+
return undefined;
|
|
390
|
+
}
|
|
391
|
+
const subject = `alert rule "${r.title}"`;
|
|
392
|
+
const id = `rule:${gi}:${ri}`;
|
|
393
|
+
const data: DeclRef[] = [];
|
|
394
|
+
r.data.forEach((q, qi) => {
|
|
395
|
+
if (!isObject(q)) {
|
|
396
|
+
this.report.drop(pointer(...path, "data", qi), subject, `data[${qi}]`, "(it is not a query)");
|
|
397
|
+
return;
|
|
398
|
+
}
|
|
399
|
+
const { className, props } = this.query(q, [...path, "data", qi], qi, subject);
|
|
400
|
+
const qid = `${id}:${qi}`;
|
|
401
|
+
const refId = typeof q.refId === "string" ? q.refId : refIdAt(qi);
|
|
402
|
+
this.declarations.push({ id: qid, kind: "new", className, props, name: { of: id, suffix: refId }, module: "rules", unit });
|
|
403
|
+
data.push(declRef(qid));
|
|
404
|
+
});
|
|
405
|
+
|
|
406
|
+
const props: Json = { title: r.title };
|
|
407
|
+
if (typeof r.uid === "string") props.uid = r.uid;
|
|
408
|
+
const lastRef = (() => {
|
|
409
|
+
const last = r.data[r.data.length - 1];
|
|
410
|
+
return isObject(last) && typeof last.refId === "string" ? last.refId : refIdAt(r.data.length - 1);
|
|
411
|
+
})();
|
|
412
|
+
const isRecord = isObject(r.record);
|
|
413
|
+
if (typeof r.condition === "string" && r.condition !== "" && (isRecord || r.condition !== lastRef)) props.condition = r.condition;
|
|
414
|
+
props.data = data;
|
|
415
|
+
for (const k of RULE_FIELDS) {
|
|
416
|
+
if (["uid", "title", "condition", "data"].includes(k)) continue;
|
|
417
|
+
const v = r[k];
|
|
418
|
+
if (v === undefined || v === null) continue;
|
|
419
|
+
if (k === "dasboardUid") {
|
|
420
|
+
if (r.dashboardUid === undefined || r.dashboardUid === "") {
|
|
421
|
+
// Grafana reads the old misspelling as dashboardUid.
|
|
422
|
+
props.dashboardUid = v;
|
|
423
|
+
this.report.edit({ op: "replace", path: pointer(...path, "dashboardUid"), value: v });
|
|
424
|
+
this.report.drop(pointer(...path, k), subject, k);
|
|
425
|
+
} else this.report.drop(pointer(...path, k), subject, k, "(dashboardUid is set too, and Grafana uses that)");
|
|
426
|
+
continue;
|
|
427
|
+
}
|
|
428
|
+
if (k === "notification_settings" && isObject(v)) {
|
|
429
|
+
const ns: Json = {};
|
|
430
|
+
for (const [nk, nv] of Object.entries(v)) {
|
|
431
|
+
if (!NOTIFICATION_SETTINGS_FIELDS.includes(nk)) {
|
|
432
|
+
this.report.drop(pointer(...path, k, nk), subject, `notification_settings.${nk}`, NO_PROP);
|
|
433
|
+
continue;
|
|
434
|
+
}
|
|
435
|
+
ns[nk] = nk === "receiver" ? this.receiverRef(nv) : nk.endsWith("time_intervals") ? this.timingRefs(nv) : nv;
|
|
436
|
+
}
|
|
437
|
+
props.notification_settings = ns;
|
|
438
|
+
continue;
|
|
439
|
+
}
|
|
440
|
+
props[k] = v;
|
|
441
|
+
}
|
|
442
|
+
for (const k of Object.keys(r)) if (!RULE_FIELDS.includes(k)) this.report.drop(pointer(...path, k), subject, k, NO_PROP);
|
|
443
|
+
this.declarations.push({ id, kind: "new", className: "AlertRule", props, name: r.title, module: "rules", unit });
|
|
444
|
+
return declRef(id);
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
private groups(list: unknown[]): void {
|
|
448
|
+
list.forEach((g, gi) => {
|
|
449
|
+
if (!isObject(g) || typeof g.name !== "string" || typeof g.folder !== "string") {
|
|
450
|
+
this.report.drop(pointer("groups", gi), "groups", `entry ${gi}`, "(it has no name or folder)");
|
|
451
|
+
return;
|
|
452
|
+
}
|
|
453
|
+
const unit = `group:${gi}`;
|
|
454
|
+
const rules = (Array.isArray(g.rules) ? g.rules : []).flatMap((r, ri) => {
|
|
455
|
+
const ref = isObject(r) ? this.rule(r, gi, ri, unit) : undefined;
|
|
456
|
+
return ref ? [ref] : [];
|
|
457
|
+
});
|
|
458
|
+
const props: Json = {};
|
|
459
|
+
for (const k of GROUP_FIELDS) if (k !== "rules" && g[k] !== undefined && g[k] !== null) props[k] = g[k];
|
|
460
|
+
props.rules = rules;
|
|
461
|
+
for (const k of Object.keys(g)) if (!GROUP_FIELDS.includes(k)) this.report.drop(pointer("groups", gi, k), `rule group "${g.name}"`, k, NO_PROP);
|
|
462
|
+
this.declarations.push({ id: unit, kind: "new", className: "AlertRuleGroup", props, name: `${g.name} rules`, module: "rules", unit });
|
|
463
|
+
});
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
convert(): Plan {
|
|
467
|
+
const d = this.doc;
|
|
468
|
+
const groups = Array.isArray(d.groups) ? d.groups : [];
|
|
469
|
+
this.scanDatasources(groups);
|
|
470
|
+
this.templates(Array.isArray(d.templates) ? d.templates : []);
|
|
471
|
+
this.muteTimes(Array.isArray(d.muteTimes) ? d.muteTimes : []);
|
|
472
|
+
this.contactPointList(Array.isArray(d.contactPoints) ? d.contactPoints : []);
|
|
473
|
+
this.policies(Array.isArray(d.policies) ? d.policies : []);
|
|
474
|
+
this.groups(groups);
|
|
475
|
+
for (const [key, what] of Object.entries(DELETIONS)) {
|
|
476
|
+
const v = d[key];
|
|
477
|
+
if (!Array.isArray(v) || v.length === 0) continue;
|
|
478
|
+
this.report.drop(pointer(key), "the provisioning file", "", `lists ${v.length} ${what} (${key}), which chant does not write; apply them to Grafana once by hand or keep that file as it is`);
|
|
479
|
+
}
|
|
480
|
+
const exported = this.declarations.filter((x) => ["AlertRuleGroup", "ContactPoint", "NotificationPolicy", "MuteTiming", "NotificationTemplate"].includes(x.className ?? ""));
|
|
481
|
+
return {
|
|
482
|
+
directory: "",
|
|
483
|
+
modules: [
|
|
484
|
+
{ key: "datasources", file: "alerting-datasources", summary: "Datasources the alert rules query, already in Grafana" },
|
|
485
|
+
{ key: "notifications", file: "notifications", summary: "Contact points, notification policies, mute timings and templates, from a Grafana alerting provisioning file" },
|
|
486
|
+
{ key: "rules", file: "alert-rules", summary: "Grafana-managed alert rules, from a Grafana alerting provisioning file" },
|
|
487
|
+
],
|
|
488
|
+
declarations: this.declarations,
|
|
489
|
+
customClasses: [],
|
|
490
|
+
exports: exported.map((x) => x.id),
|
|
491
|
+
};
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
/** Plan an alerting provisioning file. `edits` applied to the file give what the rebuilt file should equal (see `normalizeAlerting`). */
|
|
496
|
+
export function planAlertingProvisioning(doc: Json): { plan: Plan; edits: ImportEdit[]; warnings: string[] } {
|
|
497
|
+
const report = new Report();
|
|
498
|
+
const plan = new AlertingConverter(doc, report).convert();
|
|
499
|
+
return { plan, edits: report.edits, warnings: report.warnings() };
|
|
500
|
+
}
|