@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.
Files changed (532) hide show
  1. package/README.md +24 -8
  2. package/dist/actions/grafana.d.ts +24 -0
  3. package/dist/actions/grafana.d.ts.map +1 -0
  4. package/dist/actions/index.d.ts +2 -0
  5. package/dist/actions/index.d.ts.map +1 -0
  6. package/dist/alerting-build.d.ts +128 -0
  7. package/dist/alerting-build.d.ts.map +1 -0
  8. package/dist/alerting.d.ts +273 -0
  9. package/dist/alerting.d.ts.map +1 -0
  10. package/dist/annotations.d.ts +61 -0
  11. package/dist/annotations.d.ts.map +1 -0
  12. package/dist/api/apply.d.ts +160 -0
  13. package/dist/api/apply.d.ts.map +1 -0
  14. package/dist/api/bind.d.ts +32 -0
  15. package/dist/api/bind.d.ts.map +1 -0
  16. package/dist/api/client.d.ts +76 -0
  17. package/dist/api/client.d.ts.map +1 -0
  18. package/dist/api/converge.d.ts +33 -0
  19. package/dist/api/converge.d.ts.map +1 -0
  20. package/dist/api/dashboards.d.ts +102 -0
  21. package/dist/api/dashboards.d.ts.map +1 -0
  22. package/dist/api/datasources.d.ts +26 -0
  23. package/dist/api/datasources.d.ts.map +1 -0
  24. package/dist/api/fake-grafana-writes.d.ts +41 -0
  25. package/dist/api/fake-grafana-writes.d.ts.map +1 -0
  26. package/dist/api/fake-grafana.d.ts +63 -0
  27. package/dist/api/fake-grafana.d.ts.map +1 -0
  28. package/dist/api/folders.d.ts +146 -0
  29. package/dist/api/folders.d.ts.map +1 -0
  30. package/dist/api/library-panels.d.ts +52 -0
  31. package/dist/api/library-panels.d.ts.map +1 -0
  32. package/dist/build.d.ts +55 -6
  33. package/dist/build.d.ts.map +1 -1
  34. package/dist/catalog.d.ts +1 -1
  35. package/dist/catalog.d.ts.map +1 -1
  36. package/dist/codegen/docs.d.ts.map +1 -1
  37. package/dist/codegen/generate.d.ts +7 -1
  38. package/dist/codegen/generate.d.ts.map +1 -1
  39. package/dist/codegen/schema-types.d.ts +3 -3
  40. package/dist/codegen/schema-types.d.ts.map +1 -1
  41. package/dist/composites/agent-dashboard.d.ts.map +1 -1
  42. package/dist/composites/catalog.d.ts.map +1 -1
  43. package/dist/composites/index.d.ts +4 -2
  44. package/dist/composites/index.d.ts.map +1 -1
  45. package/dist/composites/red-dashboard.d.ts +21 -2
  46. package/dist/composites/red-dashboard.d.ts.map +1 -1
  47. package/dist/composites/shared.d.ts +11 -3
  48. package/dist/composites/shared.d.ts.map +1 -1
  49. package/dist/composites/slo-alert-rules.d.ts +92 -0
  50. package/dist/composites/slo-alert-rules.d.ts.map +1 -0
  51. package/dist/config.d.ts +111 -0
  52. package/dist/config.d.ts.map +1 -0
  53. package/dist/contact-point-secrets.d.ts +17 -0
  54. package/dist/contact-point-secrets.d.ts.map +1 -0
  55. package/dist/dashboard.d.ts +51 -9
  56. package/dist/dashboard.d.ts.map +1 -1
  57. package/dist/datasource-refs.d.ts +99 -0
  58. package/dist/datasource-refs.d.ts.map +1 -0
  59. package/dist/datasource-settings.d.ts +834 -0
  60. package/dist/datasource-settings.d.ts.map +1 -0
  61. package/dist/datasource.d.ts +82 -11
  62. package/dist/datasource.d.ts.map +1 -1
  63. package/dist/deep-observe-hooks.d.ts +64 -0
  64. package/dist/deep-observe-hooks.d.ts.map +1 -0
  65. package/dist/deep-observe.d.ts +47 -0
  66. package/dist/deep-observe.d.ts.map +1 -0
  67. package/dist/describe-resources.d.ts +50 -0
  68. package/dist/describe-resources.d.ts.map +1 -0
  69. package/dist/detect.d.ts +32 -3
  70. package/dist/detect.d.ts.map +1 -1
  71. package/dist/export-resources.d.ts +38 -0
  72. package/dist/export-resources.d.ts.map +1 -0
  73. package/dist/folder.d.ts +68 -0
  74. package/dist/folder.d.ts.map +1 -0
  75. package/dist/import/alerting.d.ts +40 -0
  76. package/dist/import/alerting.d.ts.map +1 -0
  77. package/dist/import/edits.d.ts +47 -0
  78. package/dist/import/edits.d.ts.map +1 -0
  79. package/dist/import/embedded.d.ts +14 -0
  80. package/dist/import/embedded.d.ts.map +1 -0
  81. package/dist/import/generator.d.ts +60 -0
  82. package/dist/import/generator.d.ts.map +1 -0
  83. package/dist/import/live-export.d.ts +108 -0
  84. package/dist/import/live-export.d.ts.map +1 -0
  85. package/dist/import/mappings.d.ts +89 -0
  86. package/dist/import/mappings.d.ts.map +1 -0
  87. package/dist/import/model.d.ts +105 -0
  88. package/dist/import/model.d.ts.map +1 -0
  89. package/dist/import/normalize-alerting.d.ts +26 -0
  90. package/dist/import/normalize-alerting.d.ts.map +1 -0
  91. package/dist/import/normalize.d.ts +74 -0
  92. package/dist/import/normalize.d.ts.map +1 -0
  93. package/dist/import/parser.d.ts +125 -0
  94. package/dist/import/parser.d.ts.map +1 -0
  95. package/dist/import/report.d.ts +26 -0
  96. package/dist/import/report.d.ts.map +1 -0
  97. package/dist/import/testdata/fixtures.d.ts +41 -0
  98. package/dist/import/testdata/fixtures.d.ts.map +1 -0
  99. package/dist/import/v2.d.ts +73 -0
  100. package/dist/import/v2.d.ts.map +1 -0
  101. package/dist/index.d.ts +31 -12
  102. package/dist/index.d.ts.map +1 -1
  103. package/dist/init-templates.d.ts +27 -0
  104. package/dist/init-templates.d.ts.map +1 -0
  105. package/dist/integrity.json +22 -12
  106. package/dist/k8s.d.ts +107 -0
  107. package/dist/k8s.d.ts.map +1 -0
  108. package/dist/lint/audit-catalog.d.ts +2 -1
  109. package/dist/lint/audit-catalog.d.ts.map +1 -1
  110. package/dist/lint/post-synth/graf101.d.ts +2 -2
  111. package/dist/lint/post-synth/graf102.d.ts +1 -1
  112. package/dist/lint/post-synth/graf104.d.ts +2 -2
  113. package/dist/lint/post-synth/graf106.d.ts +2 -2
  114. package/dist/lint/post-synth/graf107.d.ts +1 -1
  115. package/dist/lint/post-synth/graf108.d.ts +8 -0
  116. package/dist/lint/post-synth/graf108.d.ts.map +1 -0
  117. package/dist/lint/post-synth/graf109.d.ts +8 -0
  118. package/dist/lint/post-synth/graf109.d.ts.map +1 -0
  119. package/dist/lint/post-synth/graf110.d.ts +8 -0
  120. package/dist/lint/post-synth/graf110.d.ts.map +1 -0
  121. package/dist/lint/post-synth/graf111.d.ts +8 -0
  122. package/dist/lint/post-synth/graf111.d.ts.map +1 -0
  123. package/dist/lint/post-synth/graf112.d.ts +8 -0
  124. package/dist/lint/post-synth/graf112.d.ts.map +1 -0
  125. package/dist/lint/post-synth/graf113.d.ts +8 -0
  126. package/dist/lint/post-synth/graf113.d.ts.map +1 -0
  127. package/dist/lint/post-synth/graf114.d.ts +8 -0
  128. package/dist/lint/post-synth/graf114.d.ts.map +1 -0
  129. package/dist/lint/post-synth/graf115.d.ts +8 -0
  130. package/dist/lint/post-synth/graf115.d.ts.map +1 -0
  131. package/dist/lint/post-synth/grafana-helpers.d.ts +7 -5
  132. package/dist/lint/post-synth/grafana-helpers.d.ts.map +1 -1
  133. package/dist/lint/post-synth/index.d.ts.map +1 -1
  134. package/dist/lint/rules/literal-secret.d.ts +7 -3
  135. package/dist/lint/rules/literal-secret.d.ts.map +1 -1
  136. package/dist/lsp/hover.d.ts.map +1 -1
  137. package/dist/manifest.json +1 -1
  138. package/dist/members.d.ts +27 -0
  139. package/dist/members.d.ts.map +1 -0
  140. package/dist/meta.json +205 -0
  141. package/dist/okf/index.md +53 -4
  142. package/dist/okf/rules/GRAF002.md +3 -2
  143. package/dist/okf/rules/GRAF101.md +3 -2
  144. package/dist/okf/rules/GRAF102.md +1 -0
  145. package/dist/okf/rules/GRAF104.md +2 -2
  146. package/dist/okf/rules/GRAF106.md +2 -2
  147. package/dist/okf/rules/GRAF108.md +11 -0
  148. package/dist/okf/rules/GRAF109.md +15 -0
  149. package/dist/okf/rules/GRAF110.md +11 -0
  150. package/dist/okf/rules/GRAF111.md +11 -0
  151. package/dist/okf/rules/GRAF112.md +16 -0
  152. package/dist/okf/rules/GRAF113.md +16 -0
  153. package/dist/okf/rules/GRAF114.md +11 -0
  154. package/dist/okf/rules/GRAF115.md +11 -0
  155. package/dist/okf/types/AdhocVariable.md +9 -0
  156. package/dist/okf/types/AlertListPanel.md +9 -0
  157. package/dist/okf/types/AlertQuery.md +9 -0
  158. package/dist/okf/types/AlertRule.md +9 -0
  159. package/dist/okf/types/AlertRuleGroup.md +9 -0
  160. package/dist/okf/types/AzureMonitorQuery.md +9 -0
  161. package/dist/okf/types/BarChartPanel.md +9 -0
  162. package/dist/okf/types/BarGaugePanel.md +9 -0
  163. package/dist/okf/types/BigQueryQuery.md +9 -0
  164. package/dist/okf/types/CanvasPanel.md +9 -0
  165. package/dist/okf/types/ClassicConditionsExpression.md +9 -0
  166. package/dist/okf/types/CloudMonitoringQuery.md +9 -0
  167. package/dist/okf/types/CloudWatchQuery.md +9 -0
  168. package/dist/okf/types/ContactPoint.md +14 -0
  169. package/dist/okf/types/Dashboard.md +2 -1
  170. package/dist/okf/types/Datasource.md +3 -2
  171. package/dist/okf/types/DatasourceProvisioning.md +9 -0
  172. package/dist/okf/types/ElasticsearchQuery.md +9 -0
  173. package/dist/okf/types/ExternalDatasource.md +15 -0
  174. package/dist/okf/types/FlameGraphPanel.md +9 -0
  175. package/dist/okf/types/Folder.md +9 -0
  176. package/dist/okf/types/GeomapPanel.md +9 -0
  177. package/dist/okf/types/GroupByVariable.md +9 -0
  178. package/dist/okf/types/HistogramPanel.md +9 -0
  179. package/dist/okf/types/MSSQLQuery.md +9 -0
  180. package/dist/okf/types/MathExpression.md +9 -0
  181. package/dist/okf/types/MuteTiming.md +13 -0
  182. package/dist/okf/types/MySQLQuery.md +9 -0
  183. package/dist/okf/types/NodeGraphPanel.md +9 -0
  184. package/dist/okf/types/NotificationPolicy.md +9 -0
  185. package/dist/okf/types/NotificationTemplate.md +9 -0
  186. package/dist/okf/types/PieChartPanel.md +9 -0
  187. package/dist/okf/types/PostgresQuery.md +9 -0
  188. package/dist/okf/types/PyroscopeQuery.md +9 -0
  189. package/dist/okf/types/ReduceExpression.md +9 -0
  190. package/dist/okf/types/ResampleExpression.md +9 -0
  191. package/dist/okf/types/SqlExpression.md +9 -0
  192. package/dist/okf/types/StateTimelinePanel.md +9 -0
  193. package/dist/okf/types/StatusHistoryPanel.md +9 -0
  194. package/dist/okf/types/SwitchVariable.md +9 -0
  195. package/dist/okf/types/ThresholdExpression.md +9 -0
  196. package/dist/okf/types/TrendPanel.md +9 -0
  197. package/dist/okf/types/XYChartPanel.md +9 -0
  198. package/dist/op/activities/grafana-apply.d.ts +86 -0
  199. package/dist/op/activities/grafana-apply.d.ts.map +1 -0
  200. package/dist/op/activities/index.d.ts +9 -0
  201. package/dist/op/activities/index.d.ts.map +1 -0
  202. package/dist/op/builders.d.ts +24 -0
  203. package/dist/op/builders.d.ts.map +1 -0
  204. package/dist/ownership.d.ts +78 -0
  205. package/dist/ownership.d.ts.map +1 -0
  206. package/dist/panel-options.d.ts +65 -0
  207. package/dist/panel-options.d.ts.map +1 -0
  208. package/dist/panels.d.ts +46 -9
  209. package/dist/panels.d.ts.map +1 -1
  210. package/dist/pin.d.ts +29 -7
  211. package/dist/pin.d.ts.map +1 -1
  212. package/dist/plugin.d.ts.map +1 -1
  213. package/dist/promql-check.d.ts +51 -0
  214. package/dist/promql-check.d.ts.map +1 -0
  215. package/dist/query-models.d.ts +95 -0
  216. package/dist/query-models.d.ts.map +1 -0
  217. package/dist/query.d.ts +116 -18
  218. package/dist/query.d.ts.map +1 -1
  219. package/dist/rules/graf101.ts +3 -3
  220. package/dist/rules/graf102.ts +1 -1
  221. package/dist/rules/graf104.ts +3 -3
  222. package/dist/rules/graf106.ts +3 -3
  223. package/dist/rules/graf107.ts +1 -1
  224. package/dist/rules/graf108.ts +17 -0
  225. package/dist/rules/graf109.ts +17 -0
  226. package/dist/rules/graf110.ts +17 -0
  227. package/dist/rules/graf111.ts +17 -0
  228. package/dist/rules/graf112.ts +17 -0
  229. package/dist/rules/graf113.ts +17 -0
  230. package/dist/rules/graf114.ts +17 -0
  231. package/dist/rules/graf115.ts +17 -0
  232. package/dist/rules/grafana-helpers.ts +27 -10
  233. package/dist/rules/literal-secret.ts +60 -6
  234. package/dist/schema/azuremonitor.gen.d.ts +354 -0
  235. package/dist/schema/azuremonitor.gen.d.ts.map +1 -0
  236. package/dist/schema/barchart.gen.d.ts +198 -0
  237. package/dist/schema/barchart.gen.d.ts.map +1 -0
  238. package/dist/schema/bargauge.gen.d.ts +97 -0
  239. package/dist/schema/bargauge.gen.d.ts.map +1 -0
  240. package/dist/schema/bigquery.gen.d.ts +93 -0
  241. package/dist/schema/bigquery.gen.d.ts.map +1 -0
  242. package/dist/schema/canvas.gen.d.ts +152 -0
  243. package/dist/schema/canvas.gen.d.ts.map +1 -0
  244. package/dist/schema/cloudwatch.gen.d.ts +394 -0
  245. package/dist/schema/cloudwatch.gen.d.ts.map +1 -0
  246. package/dist/schema/dashboard.gen.d.ts +196 -21
  247. package/dist/schema/dashboard.gen.d.ts.map +1 -1
  248. package/dist/schema/elasticsearch.gen.d.ts +444 -0
  249. package/dist/schema/elasticsearch.gen.d.ts.map +1 -0
  250. package/dist/schema/expr.gen.d.ts +606 -0
  251. package/dist/schema/expr.gen.d.ts.map +1 -0
  252. package/dist/schema/gauge.gen.d.ts.map +1 -1
  253. package/dist/schema/geomap.gen.d.ts +100 -0
  254. package/dist/schema/geomap.gen.d.ts.map +1 -0
  255. package/dist/schema/googlecloudmonitoring.gen.d.ts +312 -0
  256. package/dist/schema/googlecloudmonitoring.gen.d.ts.map +1 -0
  257. package/dist/schema/grafanapyroscope.gen.d.ts +60 -0
  258. package/dist/schema/grafanapyroscope.gen.d.ts.map +1 -0
  259. package/dist/schema/heatmap.gen.d.ts.map +1 -1
  260. package/dist/schema/histogram.gen.d.ts +132 -0
  261. package/dist/schema/histogram.gen.d.ts.map +1 -0
  262. package/dist/schema/index.d.ts +18 -0
  263. package/dist/schema/index.d.ts.map +1 -1
  264. package/dist/schema/logs.gen.d.ts +1 -0
  265. package/dist/schema/logs.gen.d.ts.map +1 -1
  266. package/dist/schema/loki.gen.d.ts.map +1 -1
  267. package/dist/schema/nodegraph.gen.d.ts +49 -0
  268. package/dist/schema/nodegraph.gen.d.ts.map +1 -0
  269. package/dist/schema/piechart.gen.d.ts +127 -0
  270. package/dist/schema/piechart.gen.d.ts.map +1 -0
  271. package/dist/schema/prometheus.gen.d.ts.map +1 -1
  272. package/dist/schema/stat.gen.d.ts.map +1 -1
  273. package/dist/schema/statetimeline.gen.d.ts +124 -0
  274. package/dist/schema/statetimeline.gen.d.ts.map +1 -0
  275. package/dist/schema/statushistory.gen.d.ts +114 -0
  276. package/dist/schema/statushistory.gen.d.ts.map +1 -0
  277. package/dist/schema/table.gen.d.ts +35 -3
  278. package/dist/schema/table.gen.d.ts.map +1 -1
  279. package/dist/schema/tempo.gen.d.ts.map +1 -1
  280. package/dist/schema/text.gen.d.ts.map +1 -1
  281. package/dist/schema/timeseries.gen.d.ts +5 -1
  282. package/dist/schema/timeseries.gen.d.ts.map +1 -1
  283. package/dist/schema/trend.gen.d.ts +180 -0
  284. package/dist/schema/trend.gen.d.ts.map +1 -0
  285. package/dist/schema/xychart.gen.d.ts +145 -0
  286. package/dist/schema/xychart.gen.d.ts.map +1 -0
  287. package/dist/schema-validate.d.ts +39 -4
  288. package/dist/schema-validate.d.ts.map +1 -1
  289. package/dist/serializer.d.ts +11 -3
  290. package/dist/serializer.d.ts.map +1 -1
  291. package/dist/skill-defs.d.ts.map +1 -1
  292. package/dist/skills/chant-grafana-alerting.md +49 -0
  293. package/dist/skills/chant-grafana-operations.md +108 -0
  294. package/dist/skills/chant-grafana-provisioning.md +3 -3
  295. package/dist/skills/chant-grafana.md +15 -8
  296. package/dist/spec/fetch-units-cli.d.ts +3 -0
  297. package/dist/spec/fetch-units-cli.d.ts.map +1 -0
  298. package/dist/spec/overlay.d.ts +43 -0
  299. package/dist/spec/overlay.d.ts.map +1 -0
  300. package/dist/spec/schemas.d.ts +21 -13
  301. package/dist/spec/schemas.d.ts.map +1 -1
  302. package/dist/spec/schemas.gen.d.ts +3 -0
  303. package/dist/spec/schemas.gen.d.ts.map +1 -0
  304. package/dist/spec/units.d.ts +35 -0
  305. package/dist/spec/units.d.ts.map +1 -0
  306. package/dist/spec/units.gen.d.ts +2 -0
  307. package/dist/spec/units.gen.d.ts.map +1 -0
  308. package/dist/transformations.d.ts +459 -0
  309. package/dist/transformations.d.ts.map +1 -0
  310. package/dist/util.d.ts +6 -0
  311. package/dist/util.d.ts.map +1 -1
  312. package/dist/validate-alerting.d.ts +65 -0
  313. package/dist/validate-alerting.d.ts.map +1 -0
  314. package/dist/validate-output.d.ts +62 -9
  315. package/dist/validate-output.d.ts.map +1 -1
  316. package/dist/validate.d.ts.map +1 -1
  317. package/dist/validation.d.ts +14 -0
  318. package/dist/validation.d.ts.map +1 -0
  319. package/dist/variables.d.ts +109 -7
  320. package/dist/variables.d.ts.map +1 -1
  321. package/package.json +12 -4
  322. package/src/actions/actions.test.ts +24 -0
  323. package/src/actions/grafana.ts +40 -0
  324. package/src/actions/index.ts +1 -0
  325. package/src/alerting-build.ts +376 -0
  326. package/src/alerting.e2e.test.ts +212 -0
  327. package/src/alerting.test.ts +272 -0
  328. package/src/alerting.ts +435 -0
  329. package/src/annotations.ts +84 -0
  330. package/src/api/apply.test.ts +256 -0
  331. package/src/api/apply.ts +394 -0
  332. package/src/api/bind.ts +58 -0
  333. package/src/api/client.ts +148 -0
  334. package/src/api/converge.ts +63 -0
  335. package/src/api/dashboards.ts +247 -0
  336. package/src/api/datasources.ts +33 -0
  337. package/src/api/fake-grafana-writes.ts +201 -0
  338. package/src/api/fake-grafana.ts +190 -0
  339. package/src/api/folders.ts +339 -0
  340. package/src/api/library-panels.ts +101 -0
  341. package/src/build.test.ts +351 -10
  342. package/src/build.ts +294 -55
  343. package/src/catalog.ts +104 -2
  344. package/src/codegen/docs.ts +50 -10
  345. package/src/codegen/generate.ts +27 -1
  346. package/src/codegen/schema-types.ts +16 -6
  347. package/src/composites/agent-dashboard.ts +6 -3
  348. package/src/composites/catalog.test.ts +2 -2
  349. package/src/composites/catalog.ts +82 -0
  350. package/src/composites/composites.test.ts +73 -9
  351. package/src/composites/index.ts +4 -2
  352. package/src/composites/queries.e2e.test.ts +374 -0
  353. package/src/composites/red-dashboard.ts +83 -12
  354. package/src/composites/shared.ts +14 -2
  355. package/src/composites/slo-alert-rules.ts +149 -0
  356. package/src/config.test.ts +61 -0
  357. package/src/config.ts +138 -0
  358. package/src/contact-point-secrets.ts +63 -0
  359. package/src/dashboard.ts +68 -9
  360. package/src/datasource-refs.test.ts +56 -0
  361. package/src/datasource-refs.ts +213 -0
  362. package/src/datasource-settings.test.ts +106 -0
  363. package/src/datasource-settings.ts +941 -0
  364. package/src/datasource.ts +123 -11
  365. package/src/deep-observe-hooks.ts +267 -0
  366. package/src/deep-observe.test.ts +311 -0
  367. package/src/deep-observe.ts +187 -0
  368. package/src/describe-resources.test.ts +271 -0
  369. package/src/describe-resources.ts +244 -0
  370. package/src/detect.ts +82 -5
  371. package/src/export-resources.test.ts +129 -0
  372. package/src/export-resources.ts +84 -0
  373. package/src/folder.ts +118 -0
  374. package/src/generated/lexicon-grafana.json +205 -0
  375. package/src/import/alerting-roundtrip.test.ts +161 -0
  376. package/src/import/alerting.ts +500 -0
  377. package/src/import/edits.ts +95 -0
  378. package/src/import/embedded.test.ts +45 -0
  379. package/src/import/embedded.ts +47 -0
  380. package/src/import/generated-types.e2e.test.ts +130 -0
  381. package/src/import/generator.test.ts +210 -0
  382. package/src/import/generator.ts +463 -0
  383. package/src/import/live-export.ts +264 -0
  384. package/src/import/mappings.ts +336 -0
  385. package/src/import/model.ts +117 -0
  386. package/src/import/normalize-alerting.ts +101 -0
  387. package/src/import/normalize.test.ts +117 -0
  388. package/src/import/normalize.ts +393 -0
  389. package/src/import/parser.test.ts +516 -0
  390. package/src/import/parser.ts +1387 -0
  391. package/src/import/report.ts +62 -0
  392. package/src/import/roundtrip.test.ts +563 -0
  393. package/src/import/testdata/fixtures.ts +137 -0
  394. package/src/import/v2.test.ts +292 -0
  395. package/src/import/v2.ts +804 -0
  396. package/src/import.e2e.test.ts +31 -84
  397. package/src/import.test.ts +9 -3
  398. package/src/index.ts +308 -14
  399. package/src/init-templates.test.ts +74 -0
  400. package/src/init-templates.ts +349 -0
  401. package/src/k8s.test.ts +98 -0
  402. package/src/k8s.ts +197 -0
  403. package/src/lint/audit-catalog.ts +68 -3
  404. package/src/lint/core-lint.test.ts +43 -0
  405. package/src/lint/post-synth/graf101.ts +3 -3
  406. package/src/lint/post-synth/graf102.ts +1 -1
  407. package/src/lint/post-synth/graf104.ts +3 -3
  408. package/src/lint/post-synth/graf106.ts +3 -3
  409. package/src/lint/post-synth/graf107.ts +1 -1
  410. package/src/lint/post-synth/graf108.ts +17 -0
  411. package/src/lint/post-synth/graf109.ts +17 -0
  412. package/src/lint/post-synth/graf110.ts +17 -0
  413. package/src/lint/post-synth/graf111.ts +17 -0
  414. package/src/lint/post-synth/graf112.ts +17 -0
  415. package/src/lint/post-synth/graf113.ts +17 -0
  416. package/src/lint/post-synth/graf114.ts +17 -0
  417. package/src/lint/post-synth/graf115.ts +17 -0
  418. package/src/lint/post-synth/grafana-helpers.ts +27 -10
  419. package/src/lint/post-synth/index.ts +16 -0
  420. package/src/lint/post-synth/post-synth.test.ts +333 -12
  421. package/src/lint/rules/literal-secret.ts +60 -6
  422. package/src/lint/rules/rules.test.ts +27 -0
  423. package/src/load-cost.test.ts +35 -0
  424. package/src/lsp/completions.test.ts +5 -0
  425. package/src/lsp/hover.test.ts +7 -0
  426. package/src/lsp/hover.ts +8 -1
  427. package/src/members.ts +68 -0
  428. package/src/observe.e2e.test.ts +255 -0
  429. package/src/op/activities/grafana-apply.e2e.test.ts +184 -0
  430. package/src/op/activities/grafana-apply.test.ts +243 -0
  431. package/src/op/activities/grafana-apply.ts +197 -0
  432. package/src/op/activities/index.ts +8 -0
  433. package/src/op/builders.test.ts +40 -0
  434. package/src/op/builders.ts +34 -0
  435. package/src/ownership.ts +122 -0
  436. package/src/panel-options.ts +66 -0
  437. package/src/panels.test.ts +53 -0
  438. package/src/panels.ts +148 -10
  439. package/src/pin.ts +69 -6
  440. package/src/plugin.test.ts +12 -2
  441. package/src/plugin.ts +75 -0
  442. package/src/promql-check.test.ts +108 -0
  443. package/src/promql-check.ts +193 -0
  444. package/src/query-models.ts +94 -0
  445. package/src/query.test.ts +166 -0
  446. package/src/query.ts +190 -20
  447. package/src/schema/azuremonitor.gen.ts +381 -0
  448. package/src/schema/barchart.gen.ts +221 -0
  449. package/src/schema/bargauge.gen.ts +111 -0
  450. package/src/schema/bigquery.gen.ts +110 -0
  451. package/src/schema/canvas.gen.ts +176 -0
  452. package/src/schema/cloudwatch.gen.ts +423 -0
  453. package/src/schema/dashboard.gen.ts +206 -22
  454. package/src/schema/elasticsearch.gen.ts +508 -0
  455. package/src/schema/expr.gen.ts +618 -0
  456. package/src/schema/gauge.gen.ts +2 -1
  457. package/src/schema/geomap.gen.ts +112 -0
  458. package/src/schema/googlecloudmonitoring.gen.ts +330 -0
  459. package/src/schema/grafanapyroscope.gen.ts +66 -0
  460. package/src/schema/heatmap.gen.ts +2 -1
  461. package/src/schema/histogram.gen.ts +151 -0
  462. package/src/schema/index.ts +18 -0
  463. package/src/schema/logs.gen.ts +3 -1
  464. package/src/schema/loki.gen.ts +2 -1
  465. package/src/schema/nodegraph.gen.ts +58 -0
  466. package/src/schema/piechart.gen.ts +146 -0
  467. package/src/schema/prometheus.gen.ts +2 -1
  468. package/src/schema/stat.gen.ts +2 -1
  469. package/src/schema/statetimeline.gen.ts +143 -0
  470. package/src/schema/statushistory.gen.ts +132 -0
  471. package/src/schema/table.gen.ts +41 -4
  472. package/src/schema/tempo.gen.ts +2 -1
  473. package/src/schema/text.gen.ts +2 -1
  474. package/src/schema/timeseries.gen.ts +7 -2
  475. package/src/schema/trend.gen.ts +208 -0
  476. package/src/schema/xychart.gen.ts +167 -0
  477. package/src/schema-overlay.test.ts +282 -0
  478. package/src/schema-validate.ts +199 -57
  479. package/src/schema.test.ts +4 -2
  480. package/src/serializer.test.ts +13 -1
  481. package/src/serializer.ts +11 -3
  482. package/src/skill-defs.ts +34 -0
  483. package/src/skills/chant-grafana-alerting.md +49 -0
  484. package/src/skills/chant-grafana-operations.md +108 -0
  485. package/src/skills/chant-grafana-provisioning.md +3 -3
  486. package/src/skills/chant-grafana.md +15 -8
  487. package/src/spec/fetch-cli.ts +3 -3
  488. package/src/spec/fetch-units-cli.ts +37 -0
  489. package/src/spec/overlay/canvas.overlay.json +142 -0
  490. package/src/spec/overlay/dashboard.overlay.json +378 -0
  491. package/src/spec/overlay/expr.overlay.json +14 -0
  492. package/src/spec/overlay/geomap.overlay.json +35 -0
  493. package/src/spec/overlay/logs.overlay.json +14 -0
  494. package/src/spec/overlay/nodegraph.overlay.json +31 -0
  495. package/src/spec/overlay/piechart.overlay.json +17 -0
  496. package/src/spec/overlay/table.overlay.json +103 -0
  497. package/src/spec/overlay/timeseries.overlay.json +21 -0
  498. package/src/spec/overlay/trend.overlay.json +17 -0
  499. package/src/spec/overlay/xychart.overlay.json +16 -0
  500. package/src/spec/overlay.ts +105 -0
  501. package/src/spec/schemas/azuremonitor.jsonschema.json +679 -0
  502. package/src/spec/schemas/barchart.jsonschema.json +419 -0
  503. package/src/spec/schemas/bargauge.jsonschema.json +224 -0
  504. package/src/spec/schemas/bigquery.jsonschema.json +290 -0
  505. package/src/spec/schemas/canvas.jsonschema.json +356 -0
  506. package/src/spec/schemas/cloudwatch.jsonschema.json +721 -0
  507. package/src/spec/schemas/dashboardv2.jsonschema.json +3387 -0
  508. package/src/spec/schemas/elasticsearch.jsonschema.json +1695 -0
  509. package/src/spec/schemas/expr.jsonschema.json +987 -0
  510. package/src/spec/schemas/geomap.jsonschema.json +220 -0
  511. package/src/spec/schemas/googlecloudmonitoring.jsonschema.json +488 -0
  512. package/src/spec/schemas/grafanapyroscope.jsonschema.json +93 -0
  513. package/src/spec/schemas/histogram.jsonschema.json +308 -0
  514. package/src/spec/schemas/nodegraph.jsonschema.json +77 -0
  515. package/src/spec/schemas/piechart.jsonschema.json +275 -0
  516. package/src/spec/schemas/statetimeline.jsonschema.json +324 -0
  517. package/src/spec/schemas/statushistory.jsonschema.json +292 -0
  518. package/src/spec/schemas/trend.jsonschema.json +449 -0
  519. package/src/spec/schemas/xychart.jsonschema.json +428 -0
  520. package/src/spec/schemas.gen.ts +40 -0
  521. package/src/spec/schemas.ts +30 -16
  522. package/src/spec/units.gen.ts +281 -0
  523. package/src/spec/units.ts +77 -0
  524. package/src/transformations.test.ts +65 -0
  525. package/src/transformations.ts +574 -0
  526. package/src/util.ts +8 -0
  527. package/src/validate-alerting.test.ts +255 -0
  528. package/src/validate-alerting.ts +430 -0
  529. package/src/validate-output.ts +427 -104
  530. package/src/validate.ts +28 -0
  531. package/src/validation.ts +27 -0
  532. package/src/variables.ts +120 -8
@@ -0,0 +1,149 @@
1
+ /**
2
+ * `SloAlertRules`: an `Slo`'s multiwindow, multi-burn-rate alerts as
3
+ * Grafana-managed alert rules, for teams that page from Grafana rather
4
+ * than from Prometheus and Alertmanager.
5
+ *
6
+ * The windows, factors, thresholds and labels come from the prometheus
7
+ * lexicon's `sloMetrics()`, the same numbers the `Slo`'s own Prometheus
8
+ * alerts use. Each burn-rate pair becomes one rule that reads the two
9
+ * error ratios the `Slo` records (`slo:sli_error:ratio_rate<window>`) as
10
+ * instant Prometheus queries, and a math expression that holds while both
11
+ * are above the pair's threshold:
12
+ *
13
+ * A = slo:sli_error:ratio_rate1h{slo="checkout"}
14
+ * B = slo:sli_error:ratio_rate5m{slo="checkout"}
15
+ * C = $A > 0.0144 && $B > 0.0144 (condition)
16
+ *
17
+ * So the `Slo`'s rule group must be loaded into the Prometheus the rules
18
+ * query: the recording rules are what make a 3-day window cheap to
19
+ * evaluate every minute.
20
+ */
21
+
22
+ import { Composite, type CompositeInstance } from "@intentius/chant/composite";
23
+ import { sloMetrics, type SloInstance, type SloMetrics } from "@intentius/chant-lexicon-prometheus/composites/slo";
24
+ import type { RuleGroupEntity } from "@intentius/chant-lexicon-prometheus/rules";
25
+ import {
26
+ AlertRule,
27
+ AlertRuleGroup,
28
+ MathExpression,
29
+ type AlertRuleGroupEntity,
30
+ type ContactPointEntity,
31
+ type ExecErrState,
32
+ type NoDataState,
33
+ } from "../alerting";
34
+ import { PromQuery, type DatasourceInput } from "../query";
35
+ import { slugUid } from "../util";
36
+ import { num, requireDatasource } from "./shared";
37
+
38
+ export interface SloAlertRulesProps {
39
+ /** The `Slo(...)` the rules alert on, its rule group, or what `sloMetrics()` returned for it. It must have alerting on. */
40
+ slo: SloInstance | RuleGroupEntity | SloMetrics;
41
+ /** The Prometheus holding the `Slo`'s recorded series: a declared `Datasource`, an `ExternalDatasource`, or `{ type: "prometheus", uid }`. */
42
+ datasource: DatasourceInput<"prometheus">;
43
+ /** The Grafana folder the rules are stored in, by title. */
44
+ folder: string;
45
+ /** The rule group's name. Defaults to the `Slo`'s group name, `slo-<name>`. */
46
+ group?: string;
47
+ /** How often the rules are evaluated, a multiple of 10s. Defaults to `1m`. */
48
+ interval?: string;
49
+ /**
50
+ * Send the alerts straight to this contact point. Left out, they go
51
+ * through the notification policy tree, which can route on the
52
+ * `severity` label (`page` or `ticket` by default).
53
+ */
54
+ contactPoint?: ContactPointEntity | string;
55
+ /** How long a pair must hold before its alert fires. Default: none; the short window already filters blips. */
56
+ for?: string;
57
+ /** More labels on every rule, e.g. `team`. */
58
+ labels?: Record<string, string>;
59
+ /** More annotations on every rule, e.g. `runbook_url`. */
60
+ annotations?: Record<string, string>;
61
+ /** What no data does. Defaults to `OK`: no recorded ratio means no traffic, so no budget is burning. */
62
+ noDataState?: NoDataState;
63
+ /** What a query error does. Defaults to `Error`. */
64
+ execErrState?: ExecErrState;
65
+ }
66
+
67
+ export type SloAlertRulesMembers = {
68
+ /** One rule per burn-rate pair, page tier first. */
69
+ rules: AlertRuleGroupEntity;
70
+ };
71
+
72
+ /** What `SloAlertRules(...)` returns: its rule group, as `rules`. */
73
+ export type SloAlertRulesInstance = CompositeInstance<SloAlertRulesMembers> & SloAlertRulesMembers;
74
+
75
+ function isMetrics(x: unknown): x is SloMetrics {
76
+ return typeof x === "object" && x !== null && "errorBudgetRemaining" in x && "burnRates" in x && "selector" in x;
77
+ }
78
+
79
+ /** A rule uid for one pair: the SLO's name cut to fit, then the windows, at most 40 characters. */
80
+ function pairUid(name: string, long: string, short: string): string {
81
+ const suffix = `-${long}-${short}`.toLowerCase();
82
+ return `${slugUid(`slo ${name}`).slice(0, 40 - suffix.length).replace(/-$/, "")}${suffix}`;
83
+ }
84
+
85
+ /** The queries and condition of each burn-rate rule, from `sloMetrics()`. Exposed for tests and for rules of your own. */
86
+ export function sloAlertQueries(m: SloMetrics) {
87
+ return m.burnRates.map((b) => ({
88
+ ...b,
89
+ uid: pairUid(m.name, b.long, b.short),
90
+ longExpr: `${b.longRecord}${m.selector}`,
91
+ shortExpr: `${b.shortRecord}${m.selector}`,
92
+ condition: `$A > ${num(b.threshold)} && $B > ${num(b.threshold)}`,
93
+ }));
94
+ }
95
+
96
+ /**
97
+ * Grafana-managed burn-rate alerts for an `Slo`: one rule per window pair,
98
+ * with the labels and annotations the `Slo`'s Prometheus alerts carry.
99
+ *
100
+ * @example
101
+ * ```ts
102
+ * import { Slo } from "@intentius/chant-lexicon-prometheus";
103
+ * import { SloAlertRules } from "@intentius/chant-lexicon-grafana";
104
+ *
105
+ * export const checkout = Slo({ name: "checkout", objective: 0.999, window: "30d", sli: { ... } });
106
+ * export const checkoutAlerts = SloAlertRules({ slo: checkout, datasource: prometheus, folder: "SLOs" });
107
+ * ```
108
+ */
109
+ export const SloAlertRules = Composite<SloAlertRulesProps, SloAlertRulesMembers>((props) => {
110
+ requireDatasource("SloAlertRules", props.datasource);
111
+ if (!props.slo) throw new Error("SloAlertRules: slo is required (an Slo(...) declaration)");
112
+ if (typeof props.folder !== "string" || props.folder.trim() === "") throw new Error("SloAlertRules: folder is required (the Grafana folder title)");
113
+ const m = isMetrics(props.slo) ? props.slo : sloMetrics(props.slo);
114
+ const pairs = sloAlertQueries(m);
115
+ if (pairs.length === 0) {
116
+ throw new Error(`SloAlertRules: the Slo "${m.name}" has alerting turned off, so it has no burn-rate windows to alert on`);
117
+ }
118
+ const ds = props.datasource;
119
+ const budget = num(m.errorBudget);
120
+ const rules = pairs.map(
121
+ (p) =>
122
+ new AlertRule({
123
+ title: `SLO ${m.name} error budget burn ${p.long}/${p.short} (${p.severity})`,
124
+ uid: p.uid,
125
+ data: [
126
+ new PromQuery({ datasource: ds, expr: p.longExpr, instant: true, range: false }),
127
+ new PromQuery({ datasource: ds, expr: p.shortExpr, instant: true, range: false }),
128
+ new MathExpression({ expression: p.condition }),
129
+ ],
130
+ condition: "C",
131
+ noDataState: props.noDataState ?? "OK",
132
+ execErrState: props.execErrState ?? "Error",
133
+ ...(props.for !== undefined ? { for: props.for } : {}),
134
+ labels: { ...(props.labels ?? {}), ...p.labels },
135
+ annotations: {
136
+ summary: `SLO ${m.name} is burning its error budget more than ${num(p.factor)}x too fast (${p.long} and ${p.short} windows)`,
137
+ description:
138
+ `The error ratio over both the last ${p.long} and the last ${p.short} is above ${num(p.threshold)} ` +
139
+ `(${num(p.factor)} times the ${budget} budget of a ${num(m.objective)} objective). ` +
140
+ `At that rate the ${m.window} error budget is gone in ${p.exhaustsIn}.`,
141
+ ...(props.annotations ?? {}),
142
+ },
143
+ ...(props.contactPoint !== undefined ? { notification_settings: { receiver: props.contactPoint } } : {}),
144
+ }),
145
+ );
146
+ return {
147
+ rules: new AlertRuleGroup({ name: props.group ?? m.group, folder: props.folder, interval: props.interval ?? "1m", rules }),
148
+ };
149
+ }, "SloAlertRules");
@@ -0,0 +1,61 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { grafanaConfigSchema, resolveGrafanaTarget } from "./config";
3
+ import { namespaceOf } from "./api/client";
4
+ import { grafanaPlugin } from "./plugin";
5
+
6
+ const config = {
7
+ grafana: {
8
+ profiles: {
9
+ prod: { url: "https://grafana.example.com/", token: { env: "GF_PROD" }, orgId: 2 },
10
+ local: { url: "http://localhost:3000", basicAuth: { user: { env: "GF_USER" }, password: { env: "GF_PASS" } } },
11
+ },
12
+ },
13
+ };
14
+
15
+ describe("grafana config namespace", () => {
16
+ it("is declared on the plugin and rejects an unknown key in a profile", () => {
17
+ expect(grafanaPlugin.configSchema).toBe(grafanaConfigSchema);
18
+ expect(grafanaConfigSchema.safeParse({ profiles: { prod: { url: "x", tokn: { env: "T" } } } }).success).toBe(false);
19
+ expect(grafanaConfigSchema.safeParse(config.grafana).success).toBe(true);
20
+ });
21
+ });
22
+
23
+ describe("resolveGrafanaTarget", () => {
24
+ it("binds an environment's profile, with the token from the variable it names", () => {
25
+ expect(resolveGrafanaTarget({ environment: "prod", config, env: { GF_PROD: "glsa_x" } })).toEqual({
26
+ url: "https://grafana.example.com",
27
+ auth: { token: "glsa_x" },
28
+ orgId: 2,
29
+ source: "grafana.profiles.prod",
30
+ });
31
+ });
32
+
33
+ it("basic auth, from two variables", () => {
34
+ expect(resolveGrafanaTarget({ environment: "local", config, env: { GF_USER: "admin", GF_PASS: "admin" } })).toMatchObject({ auth: { user: "admin", password: "admin" } });
35
+ });
36
+
37
+ it("a profile whose credential variable is unset is no-credentials, not an anonymous read", () => {
38
+ expect(resolveGrafanaTarget({ environment: "prod", config, env: {} })).toEqual({ reason: "no-credentials", detail: expect.stringContaining("GF_PROD") });
39
+ expect(resolveGrafanaTarget({ environment: "local", config, env: { GF_USER: "a" } })).toEqual({ reason: "no-credentials", detail: expect.stringContaining("GF_PASS") });
40
+ });
41
+
42
+ it("falls back to GRAFANA_URL and GRAFANA_TOKEN, or GRAFANA_USER and GRAFANA_PASSWORD", () => {
43
+ expect(resolveGrafanaTarget({ environment: "dev", config, env: { GRAFANA_URL: "http://g:3000", GRAFANA_TOKEN: "t", GRAFANA_ORG_ID: "3" } })).toEqual({
44
+ url: "http://g:3000",
45
+ auth: { token: "t" },
46
+ orgId: 3,
47
+ source: "env GRAFANA_URL",
48
+ });
49
+ expect(resolveGrafanaTarget({ env: { GRAFANA_URL: "http://g", GRAFANA_USER: "u", GRAFANA_PASSWORD: "p" } })).toMatchObject({ auth: { user: "u", password: "p" } });
50
+ });
51
+
52
+ it("no profile and no GRAFANA_URL is no-binding", () => {
53
+ expect(resolveGrafanaTarget({ environment: "dev", config, env: {} })).toEqual({ reason: "no-binding", detail: expect.stringContaining("grafana.profiles.dev") });
54
+ });
55
+
56
+ it("the dashboard API namespace follows the organisation", () => {
57
+ expect(namespaceOf({})).toBe("default");
58
+ expect(namespaceOf({ orgId: 4 })).toBe("org-4");
59
+ expect(namespaceOf({ orgId: 4, namespace: "stacks-12" })).toBe("stacks-12");
60
+ });
61
+ });
package/src/config.ts ADDED
@@ -0,0 +1,138 @@
1
+ /**
2
+ * The `grafana` namespace in `chant.config.ts` (#2946): which Grafana each
3
+ * chant environment observes, exports from and (#2948) applies to.
4
+ *
5
+ * ```ts
6
+ * import type { ChantConfig } from "@intentius/chant/config";
7
+ * import "@intentius/chant-lexicon-grafana"; // brings the `grafana` key into ChantConfig
8
+ *
9
+ * export default {
10
+ * lexicons: ["grafana"],
11
+ * grafana: {
12
+ * profiles: {
13
+ * staging: { url: "https://grafana.staging.example.com", token: { env: "GRAFANA_STAGING_TOKEN" } },
14
+ * prod: { url: "https://grafana.example.com", token: { env: "GRAFANA_PROD_TOKEN" }, orgId: 2 },
15
+ * },
16
+ * },
17
+ * } satisfies ChantConfig;
18
+ * ```
19
+ *
20
+ * A profile is keyed by chant environment, as `k8s.profiles.<env>` is.
21
+ * Credentials are named by their environment variable, never written in
22
+ * the config. An environment with no profile falls back to `GRAFANA_URL`
23
+ * with `GRAFANA_TOKEN`, or `GRAFANA_USER` and `GRAFANA_PASSWORD` (see
24
+ * `resolveGrafanaTarget`).
25
+ *
26
+ * One schema, three consumers, as in the fountain lexicon: core validates the
27
+ * namespace against it at load, the type is derived from it, and that type
28
+ * augments `ChantConfig`.
29
+ */
30
+
31
+ import { z } from "zod";
32
+ import type { ChantConfig } from "@intentius/chant/config";
33
+ import type { GrafanaAuth, GrafanaTarget } from "./api/client";
34
+
35
+ const envRef = z.strictObject({ env: z.string() });
36
+
37
+ export const grafanaProfileSchema = z.strictObject({
38
+ /** Base URL of the Grafana instance. */
39
+ url: z.string(),
40
+ /** A service account token, named by its environment variable. */
41
+ token: envRef.optional(),
42
+ /** Basic auth, each half named by its environment variable. For a local instance; use a token elsewhere. */
43
+ basicAuth: z.strictObject({ user: envRef, password: envRef }).optional(),
44
+ /** Organisation id. Defaults to 1. */
45
+ orgId: z.number().int().positive().optional(),
46
+ /** The `dashboard.grafana.app` namespace, when it is not `default` / `org-<id>` (Grafana Cloud: `stacks-<id>`). */
47
+ namespace: z.string().optional(),
48
+ });
49
+
50
+ export const grafanaConfigSchema = z.strictObject({
51
+ /** One Grafana per chant environment. */
52
+ profiles: z.record(z.string(), grafanaProfileSchema).optional(),
53
+ });
54
+
55
+ export type GrafanaProfile = z.infer<typeof grafanaProfileSchema>;
56
+ export type GrafanaConfig = z.infer<typeof grafanaConfigSchema>;
57
+
58
+ declare module "@intentius/chant/config" {
59
+ interface ChantConfig {
60
+ grafana?: GrafanaConfig;
61
+ }
62
+ }
63
+
64
+ /** Compile-time proof that the augmentation reaches `ChantConfig`. */
65
+ export type GrafanaConfigNamespace = NonNullable<ChantConfig["grafana"]>;
66
+
67
+ /** Why no target could be resolved, in the observation vocabulary. */
68
+ export interface UnresolvedTarget {
69
+ readonly reason: "no-binding" | "no-credentials";
70
+ readonly detail: string;
71
+ }
72
+
73
+ function isUnresolved(v: GrafanaTarget | UnresolvedTarget): v is UnresolvedTarget {
74
+ return "reason" in v;
75
+ }
76
+
77
+ export { isUnresolved as isUnresolvedTarget };
78
+
79
+ /**
80
+ * The Grafana an environment reads from. Pure: `config` and `env` are
81
+ * passed in.
82
+ *
83
+ * 1. `grafana.profiles.<environment>`. A profile that names a credential
84
+ * variable which is not set is `no-credentials`, not an anonymous read:
85
+ * the profile said how to authenticate.
86
+ * 2. `GRAFANA_URL`, with `GRAFANA_TOKEN`, or `GRAFANA_USER` and
87
+ * `GRAFANA_PASSWORD`, or neither (an instance with anonymous access; a
88
+ * refused read is then `no-credentials`).
89
+ * 3. Otherwise `no-binding`.
90
+ */
91
+ export function resolveGrafanaTarget(input: {
92
+ environment?: string;
93
+ config?: Pick<ChantConfig, "grafana">;
94
+ env?: Record<string, string | undefined>;
95
+ }): GrafanaTarget | UnresolvedTarget {
96
+ const env = input.env ?? process.env;
97
+ const profile = input.environment !== undefined ? input.config?.grafana?.profiles?.[input.environment] : undefined;
98
+ if (profile) {
99
+ const source = `grafana.profiles.${input.environment}`;
100
+ let auth: GrafanaAuth | undefined;
101
+ if (profile.token) {
102
+ const token = env[profile.token.env];
103
+ if (!token) return { reason: "no-credentials", detail: `${source}.token names ${profile.token.env}, which is not set` };
104
+ auth = { token };
105
+ } else if (profile.basicAuth) {
106
+ const user = env[profile.basicAuth.user.env];
107
+ const password = env[profile.basicAuth.password.env];
108
+ if (!user || !password) {
109
+ const missing = [!user ? profile.basicAuth.user.env : undefined, !password ? profile.basicAuth.password.env : undefined].filter(Boolean).join(" and ");
110
+ return { reason: "no-credentials", detail: `${source}.basicAuth names ${missing}, which ${missing.includes(" and ") ? "are" : "is"} not set` };
111
+ }
112
+ auth = { user, password };
113
+ }
114
+ return {
115
+ url: profile.url.replace(/\/+$/, ""),
116
+ ...(auth ? { auth } : {}),
117
+ ...(profile.orgId !== undefined ? { orgId: profile.orgId } : {}),
118
+ ...(profile.namespace ? { namespace: profile.namespace } : {}),
119
+ source,
120
+ };
121
+ }
122
+
123
+ const url = env.GRAFANA_URL;
124
+ if (!url) {
125
+ const where = input.environment !== undefined ? `grafana.profiles.${input.environment} is not declared and ` : "";
126
+ return { reason: "no-binding", detail: `${where}GRAFANA_URL is not set, so there is no Grafana to read` };
127
+ }
128
+ let auth: GrafanaAuth | undefined;
129
+ if (env.GRAFANA_TOKEN) auth = { token: env.GRAFANA_TOKEN };
130
+ else if (env.GRAFANA_USER && env.GRAFANA_PASSWORD) auth = { user: env.GRAFANA_USER, password: env.GRAFANA_PASSWORD };
131
+ const orgId = env.GRAFANA_ORG_ID ? Number(env.GRAFANA_ORG_ID) : undefined;
132
+ return {
133
+ url: url.replace(/\/+$/, ""),
134
+ ...(auth ? { auth } : {}),
135
+ ...(orgId !== undefined && Number.isInteger(orgId) && orgId > 0 ? { orgId } : {}),
136
+ source: "env GRAFANA_URL",
137
+ };
138
+ }
@@ -0,0 +1,63 @@
1
+ /**
2
+ * The contact point settings Grafana stores encrypted, per integration: the
3
+ * options marked `secure` by `GET /api/alert-notifiers?version=2` on
4
+ * `grafana/grafana:13.2.2` (commit 1bea008f; the same list on 12.4.11).
5
+ * Nested settings are dotted paths (`tlsConfig.clientKey`).
6
+ *
7
+ * GRAF002 flags a literal at one of these paths, and the importer replaces
8
+ * the `[REDACTED]` that an export without secrets writes there.
9
+ */
10
+ export const CONTACT_POINT_SECRET_SETTINGS: Readonly<Record<string, readonly string[]>> = Object.freeze({
11
+ dingding: ["url"],
12
+ discord: ["url"],
13
+ email: [],
14
+ googlechat: ["url"],
15
+ jira: ["user", "password", "api_token"],
16
+ kafka: ["password"],
17
+ line: ["token"],
18
+ mqtt: ["password", "tlsConfig.caCertificate", "tlsConfig.clientCertificate", "tlsConfig.clientKey"],
19
+ oncall: ["password", "authorization_credentials"],
20
+ opsgenie: ["apiKey"],
21
+ pagerduty: ["integrationKey"],
22
+ "prometheus-alertmanager": ["basicAuthPassword"],
23
+ pushover: ["apiToken", "userKey"],
24
+ sensugo: ["apikey"],
25
+ slack: ["token", "url"],
26
+ sns: ["sigv4.access_key", "sigv4.secret_key"],
27
+ teams: [],
28
+ telegram: ["bottoken"],
29
+ threema: ["api_secret"],
30
+ victorops: ["url"],
31
+ webex: ["bot_token"],
32
+ webhook: [
33
+ "password",
34
+ "authorization_credentials",
35
+ "tlsConfig.caCertificate",
36
+ "tlsConfig.clientCertificate",
37
+ "tlsConfig.clientKey",
38
+ "hmacConfig.secret",
39
+ "http_config.oauth2.client_secret",
40
+ "http_config.oauth2.tls_config.caCertificate",
41
+ "http_config.oauth2.tls_config.clientCertificate",
42
+ "http_config.oauth2.tls_config.clientKey",
43
+ ],
44
+ wechat: ["api_secret", "http_config.basic_auth.password", "http_config.authorization.credentials", "http_config.oauth2.client_secret"],
45
+ wecom: ["url", "secret"],
46
+ });
47
+
48
+ /** How Grafana provisioning reads a value from outside the file: `$VAR`, `${VAR}`, `$__env{…}`, `$__file{…}`, `$__vault{…}`. */
49
+ export const EXPANDED_VALUE = /\$(\{[A-Za-z_][A-Za-z0-9_]*\}|[A-Za-z_][A-Za-z0-9_]*|__(env|file|vault)\{[^}]+\})/;
50
+
51
+ /** What an export without secrets writes in place of each one. */
52
+ export const REDACTED = "[REDACTED]";
53
+
54
+ /** The secret settings of one receiver present in `settings`, with their values: `[path, value]`. */
55
+ export function secretSettings(type: string, settings: Record<string, unknown>): Array<[string, unknown]> {
56
+ const out: Array<[string, unknown]> = [];
57
+ for (const path of CONTACT_POINT_SECRET_SETTINGS[type] ?? []) {
58
+ let node: unknown = settings;
59
+ for (const seg of path.split(".")) node = node && typeof node === "object" ? (node as Record<string, unknown>)[seg] : undefined;
60
+ if (node !== undefined) out.push([path, node]);
61
+ }
62
+ return out;
63
+ }
package/src/dashboard.ts CHANGED
@@ -12,6 +12,8 @@ import type { Declarable } from "@intentius/chant/declarable";
12
12
  import type { PanelEntity, RowEntity } from "./panels";
13
13
  import type { VariableEntity } from "./variables";
14
14
  import type { DashboardLink, TimePickerConfig } from "./schema/dashboard.gen";
15
+ import { isFolderEntity, type FolderEntity } from "./folder";
16
+ import type { AnnotationInput } from "./annotations";
15
17
 
16
18
  /** A dashboard link: `title` and `type` required, the rest defaulted as Grafana's editor would. */
17
19
  export type DashboardLinkInput = Partial<DashboardLink> & Pick<DashboardLink, "title" | "type">;
@@ -40,6 +42,13 @@ export interface DashboardProps {
40
42
  graphTooltip?: "default" | "sharedCrosshair" | "sharedTooltip";
41
43
  /** Whether users may edit it in the UI. Defaults to true, as in Grafana. */
42
44
  editable?: boolean;
45
+ /**
46
+ * The dashboard schema version the JSON is written at. Leave it out for
47
+ * the pinned one (`DASHBOARD_SCHEMA_VERSION`). `chant import` sets it for
48
+ * a dashboard saved by an older Grafana, so Grafana still runs its
49
+ * migrations when it loads the rebuilt JSON.
50
+ */
51
+ schemaVersion?: number;
43
52
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
44
53
  variables?: VariableEntity<any>[];
45
54
  /** Panels and rows, top to bottom. A row's own `panels` follow it. */
@@ -47,23 +56,50 @@ export interface DashboardProps {
47
56
  panels?: Array<PanelEntity<any, any> | RowEntity>;
48
57
  links?: DashboardLinkInput[];
49
58
  /**
50
- * The Grafana folder to provision it into. Written as a subdirectory of
51
- * `dashboards/`, which the provider maps to folders
52
- * (`foldersFromFilesStructure`). Leave it out for the General folder.
59
+ * Annotation queries: events drawn on the dashboard's time series panels
60
+ * (deploys, incidents), each from a datasource. Grafana adds its own
61
+ * "Annotations & Alerts" query to every dashboard that has none with
62
+ * `builtIn: 1`, so leave that one out unless you change it.
53
63
  */
54
- folder?: string;
64
+ annotations?: AnnotationInput[];
65
+ /**
66
+ * The Grafana folder: a path (`"Platform/Kubernetes"`) or a `Folder`,
67
+ * which pins the folder's uid. Written as a subdirectory of `dashboards/`,
68
+ * which the provider maps to folders (`foldersFromFilesStructure`); a path
69
+ * nests one directory in another, which Grafana 13.1 and later make a
70
+ * Kubernetes folder inside Platform, and earlier versions use only the
71
+ * last level (GRAF109 warns). The API applier creates each level with its
72
+ * uid. Leave it out for the General folder.
73
+ */
74
+ folder?: string | FolderEntity;
55
75
  }
56
76
 
77
+ /** A dashboard's props as the entity holds them: `folder` is always the path, and a `Folder` given for it is `folderEntity`. */
78
+ export type DashboardEntityProps = Omit<DashboardProps, "folder"> & { folder?: string };
79
+
57
80
  export interface DashboardEntity extends Declarable {
58
- readonly props: DashboardProps;
81
+ readonly props: DashboardEntityProps;
82
+ /** The `Folder` the dashboard was given, when it was given one rather than a path. */
83
+ readonly folderEntity?: FolderEntity;
59
84
  }
60
85
 
61
86
  export const DASHBOARD_TYPE = "Grafana::Dashboard";
62
87
 
63
88
  const DashboardBase = createResource(DASHBOARD_TYPE, "grafana", {}) as unknown as (this: object, props: Record<string, unknown>) => void;
64
89
 
65
- /** A Grafana dashboard. */
90
+ /**
91
+ * A Grafana dashboard. A `Folder` given as its `folder` is held as the
92
+ * folder's path, so everything that reads the dashboard's props (the build,
93
+ * the checks, observation's diff) sees one form; the `Folder` itself, for its
94
+ * uid, is `folderEntity`.
95
+ */
66
96
  export const Dashboard = function (this: object, props: DashboardProps) {
97
+ const folder = props.folder;
98
+ if (isFolderEntity(folder)) {
99
+ DashboardBase.call(this, { ...props, folder: folder.path } as unknown as Record<string, unknown>);
100
+ Object.defineProperty(this, "folderEntity", { value: folder, enumerable: false });
101
+ return;
102
+ }
67
103
  DashboardBase.call(this, props as unknown as Record<string, unknown>);
68
104
  } as unknown as new (props: DashboardProps) => DashboardEntity;
69
105
  Object.defineProperty(Dashboard, "name", { value: "Dashboard" });
@@ -84,8 +120,15 @@ export interface DashboardProviderProps {
84
120
  /** Provider name, unique in the provisioning file. */
85
121
  name: string;
86
122
  orgId?: number;
87
- /** Folder for dashboards not in a subdirectory. */
88
- folder?: string;
123
+ /**
124
+ * Put every dashboard this provider loads in this folder: a title, or a
125
+ * root-level `Folder`, whose title and uid are written as `folder` and
126
+ * `folderUid` (Grafana creates a provider's folder at the root, so a
127
+ * nested `Folder` is refused). Setting it (or `folderUid`) turns
128
+ * `foldersFromFilesStructure` off by default, so each dashboard's own
129
+ * `folder` is ignored (GRAF109 warns).
130
+ */
131
+ folder?: string | FolderEntity;
89
132
  folderUid?: string;
90
133
  /** Where the `dashboards/` output is mounted in the Grafana container. Defaults to `/var/lib/grafana/dashboards`. */
91
134
  path?: string;
@@ -96,8 +139,11 @@ export interface DashboardProviderProps {
96
139
  updateIntervalSeconds?: number;
97
140
  }
98
141
 
142
+ /** A provider's props as the entity holds them: a `Folder` given for `folder` is written out as `folder` and `folderUid`. */
143
+ export type DashboardProviderEntityProps = Omit<DashboardProviderProps, "folder"> & { folder?: string };
144
+
99
145
  export interface DashboardProviderEntity extends Declarable {
100
- readonly props: DashboardProviderProps;
146
+ readonly props: DashboardProviderEntityProps;
101
147
  }
102
148
 
103
149
  export const DASHBOARD_PROVIDER_TYPE = "Grafana::DashboardProvider";
@@ -109,6 +155,19 @@ const ProviderBase = createResource(DASHBOARD_PROVIDER_TYPE, "grafana", {}) as u
109
155
 
110
156
  /** An entry in the dashboard provisioning file. Optional: a default one is written when none is declared. */
111
157
  export const DashboardProvider = function (this: object, props: DashboardProviderProps) {
158
+ const folder = props.folder;
159
+ if (isFolderEntity(folder)) {
160
+ if (folder.props.parent) {
161
+ throw new Error(
162
+ `grafana: DashboardProvider "${props.name}" puts its dashboards in the Folder "${folder.path}", which is nested; Grafana creates a provider's folder at the root. Give it a root-level Folder, or leave folder out and set each dashboard's folder`,
163
+ );
164
+ }
165
+ if (props.folderUid !== undefined && props.folderUid !== folder.uid) {
166
+ throw new Error(`grafana: DashboardProvider "${props.name}" has folderUid "${props.folderUid}", but its Folder's uid is "${folder.uid}"`);
167
+ }
168
+ ProviderBase.call(this, { ...props, folder: folder.props.title, folderUid: folder.uid } as unknown as Record<string, unknown>);
169
+ return;
170
+ }
112
171
  ProviderBase.call(this, props as unknown as Record<string, unknown>);
113
172
  } as unknown as new (props: DashboardProviderProps) => DashboardProviderEntity;
114
173
  Object.defineProperty(DashboardProvider, "name", { value: "DashboardProvider" });
@@ -0,0 +1,56 @@
1
+ import { describe, expect, test } from "vitest";
2
+ import { datasourceUses, knownDatasources, resolveDatasourceRef } from "./datasource-refs";
3
+
4
+ const known = knownDatasources([{ name: "Prometheus", type: "prometheus", uid: "prom" }], [{ type: "loki", uid: "logs", name: "Logs" }]);
5
+
6
+ function dashboard(panels: unknown[], variables: unknown[] = []) {
7
+ return { uid: "d", title: "D", panels, templating: { list: variables } };
8
+ }
9
+
10
+ describe("resolveDatasourceRef", () => {
11
+ const vars = new Map([["ds", { type: "datasource", name: "ds", query: "prometheus" }]]);
12
+
13
+ test("resolves provisioned and external datasources to their plugin type", () => {
14
+ expect(resolveDatasourceRef({ uid: "prom" }, vars, known)).toMatchObject({ kind: "declared", type: "prometheus", datasource: { external: false } });
15
+ expect(resolveDatasourceRef({ type: "loki", uid: "logs" }, vars, known)).toMatchObject({ kind: "declared", type: "loki", datasource: { external: true, name: "Logs" } });
16
+ });
17
+
18
+ test("resolves a datasource variable to its plugin type", () => {
19
+ expect(resolveDatasourceRef({ uid: "${ds}" }, vars, known)).toEqual({ kind: "variable", variable: "ds", declared: true, type: "prometheus" });
20
+ expect(resolveDatasourceRef({ type: "tempo", uid: "$other" }, vars, known)).toEqual({ kind: "variable", variable: "other", declared: false, type: "tempo" });
21
+ });
22
+
23
+ test("separates Grafana's pseudo-datasources, unknown uids and no ref", () => {
24
+ expect(resolveDatasourceRef({ type: "datasource", uid: "-- Mixed --" }, vars, known).kind).toBe("builtin");
25
+ expect(resolveDatasourceRef({ type: "prometheus", uid: "gone" }, vars, known)).toEqual({ kind: "undeclared", uid: "gone", type: "prometheus" });
26
+ expect(resolveDatasourceRef(undefined, vars, known)).toEqual({ kind: "default" });
27
+ expect(resolveDatasourceRef({ type: "prometheus" }, vars, known)).toEqual({ kind: "default", type: "prometheus" });
28
+ });
29
+ });
30
+
31
+ describe("datasourceUses", () => {
32
+ test("a query without its own ref goes where its panel's goes", () => {
33
+ const d = dashboard([{ type: "stat", id: 1, title: "p", datasource: { uid: "prom" }, targets: [{ refId: "A", expr: "up" }, { refId: "B", datasource: { uid: "logs" } }] }]);
34
+ const queries = datasourceUses(d, known).filter((u) => u.kind === "query");
35
+ expect(queries.map((u) => [u.target?.refId, u.ref === undefined, u.resolved.type])).toEqual([
36
+ ["A", true, "prometheus"],
37
+ ["B", false, "loki"],
38
+ ]);
39
+ });
40
+
41
+ test("a query under a -- Mixed -- panel with no ref goes to the default datasource", () => {
42
+ const d = dashboard([{ type: "table", id: 1, datasource: { type: "datasource", uid: "-- Mixed --" }, targets: [{ refId: "A" }] }]);
43
+ expect(datasourceUses(d, known).find((u) => u.kind === "query")?.resolved).toEqual({ kind: "default" });
44
+ });
45
+
46
+ test("covers panels in collapsed rows and query variables, and skips rows", () => {
47
+ const d = dashboard(
48
+ [{ type: "row", id: 1, collapsed: true, datasource: { uid: "prom" }, panels: [{ type: "stat", id: 2, datasource: { uid: "logs" }, targets: [] }] }],
49
+ [{ type: "query", name: "job", datasource: { uid: "prom" }, query: "label_values(job)" }],
50
+ );
51
+ expect(datasourceUses(d, known).map((u) => [u.kind, u.where, u.resolved.type])).toEqual([
52
+ ["panel", 'panel (untitled) (id 2)', "loki"],
53
+ ["variable", 'variable "job"', "prometheus"],
54
+ ]);
55
+ });
56
+ });