@intentius/chant-lexicon-grafana 0.96.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 +50 -0
- package/dist/build.d.ts +116 -0
- package/dist/build.d.ts.map +1 -0
- package/dist/catalog.d.ts +26 -0
- package/dist/catalog.d.ts.map +1 -0
- package/dist/codegen/docs-cli.d.ts +3 -0
- package/dist/codegen/docs-cli.d.ts.map +1 -0
- package/dist/codegen/docs.d.ts +8 -0
- package/dist/codegen/docs.d.ts.map +1 -0
- package/dist/codegen/generate-cli.d.ts +3 -0
- package/dist/codegen/generate-cli.d.ts.map +1 -0
- package/dist/codegen/generate.d.ts +21 -0
- package/dist/codegen/generate.d.ts.map +1 -0
- package/dist/codegen/package.d.ts +7 -0
- package/dist/codegen/package.d.ts.map +1 -0
- package/dist/codegen/schema-types.d.ts +41 -0
- package/dist/codegen/schema-types.d.ts.map +1 -0
- package/dist/composites/agent-dashboard.d.ts +70 -0
- package/dist/composites/agent-dashboard.d.ts.map +1 -0
- package/dist/composites/catalog.d.ts +3 -0
- package/dist/composites/catalog.d.ts.map +1 -0
- package/dist/composites/index.d.ts +12 -0
- package/dist/composites/index.d.ts.map +1 -0
- package/dist/composites/red-dashboard.d.ts +58 -0
- package/dist/composites/red-dashboard.d.ts.map +1 -0
- package/dist/composites/shared.d.ts +68 -0
- package/dist/composites/shared.d.ts.map +1 -0
- package/dist/composites/slo-dashboard.d.ts +62 -0
- package/dist/composites/slo-dashboard.d.ts.map +1 -0
- package/dist/dashboard.d.ts +84 -0
- package/dist/dashboard.d.ts.map +1 -0
- package/dist/datasource.d.ts +67 -0
- package/dist/datasource.d.ts.map +1 -0
- package/dist/detect.d.ts +20 -0
- package/dist/detect.d.ts.map +1 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/integrity.json +22 -0
- package/dist/lint/audit-catalog.d.ts +13 -0
- package/dist/lint/audit-catalog.d.ts.map +1 -0
- package/dist/lint/post-synth/graf101.d.ts +8 -0
- package/dist/lint/post-synth/graf101.d.ts.map +1 -0
- package/dist/lint/post-synth/graf102.d.ts +8 -0
- package/dist/lint/post-synth/graf102.d.ts.map +1 -0
- package/dist/lint/post-synth/graf103.d.ts +8 -0
- package/dist/lint/post-synth/graf103.d.ts.map +1 -0
- package/dist/lint/post-synth/graf104.d.ts +8 -0
- package/dist/lint/post-synth/graf104.d.ts.map +1 -0
- package/dist/lint/post-synth/graf105.d.ts +8 -0
- package/dist/lint/post-synth/graf105.d.ts.map +1 -0
- package/dist/lint/post-synth/graf106.d.ts +8 -0
- package/dist/lint/post-synth/graf106.d.ts.map +1 -0
- package/dist/lint/post-synth/graf107.d.ts +8 -0
- package/dist/lint/post-synth/graf107.d.ts.map +1 -0
- package/dist/lint/post-synth/grafana-helpers.d.ts +16 -0
- package/dist/lint/post-synth/grafana-helpers.d.ts.map +1 -0
- package/dist/lint/post-synth/index.d.ts +3 -0
- package/dist/lint/post-synth/index.d.ts.map +1 -0
- package/dist/lint/rules/grafana-ast.d.ts +21 -0
- package/dist/lint/rules/grafana-ast.d.ts.map +1 -0
- package/dist/lint/rules/index.d.ts +6 -0
- package/dist/lint/rules/index.d.ts.map +1 -0
- package/dist/lint/rules/literal-secret.d.ts +12 -0
- package/dist/lint/rules/literal-secret.d.ts.map +1 -0
- package/dist/lint/rules/uid-syntax.d.ts +12 -0
- package/dist/lint/rules/uid-syntax.d.ts.map +1 -0
- package/dist/lsp/completions.d.ts +4 -0
- package/dist/lsp/completions.d.ts.map +1 -0
- package/dist/lsp/hover.d.ts +4 -0
- package/dist/lsp/hover.d.ts.map +1 -0
- package/dist/manifest.json +8 -0
- package/dist/meta.json +107 -0
- package/dist/okf/index.md +39 -0
- package/dist/okf/rules/GRAF001.md +16 -0
- package/dist/okf/rules/GRAF002.md +15 -0
- package/dist/okf/rules/GRAF101.md +15 -0
- package/dist/okf/rules/GRAF102.md +15 -0
- package/dist/okf/rules/GRAF103.md +11 -0
- package/dist/okf/rules/GRAF104.md +15 -0
- package/dist/okf/rules/GRAF105.md +11 -0
- package/dist/okf/rules/GRAF106.md +11 -0
- package/dist/okf/rules/GRAF107.md +11 -0
- package/dist/okf/types/ConstantVariable.md +9 -0
- package/dist/okf/types/CustomVariable.md +9 -0
- package/dist/okf/types/Dashboard.md +14 -0
- package/dist/okf/types/DashboardProvider.md +9 -0
- package/dist/okf/types/Datasource.md +16 -0
- package/dist/okf/types/DatasourceVariable.md +9 -0
- package/dist/okf/types/GaugePanel.md +9 -0
- package/dist/okf/types/HeatmapPanel.md +9 -0
- package/dist/okf/types/IntervalVariable.md +9 -0
- package/dist/okf/types/LogsPanel.md +9 -0
- package/dist/okf/types/LokiQuery.md +9 -0
- package/dist/okf/types/PromQuery.md +9 -0
- package/dist/okf/types/QueryVariable.md +9 -0
- package/dist/okf/types/Row.md +9 -0
- package/dist/okf/types/StatPanel.md +9 -0
- package/dist/okf/types/TablePanel.md +9 -0
- package/dist/okf/types/TempoQuery.md +9 -0
- package/dist/okf/types/TextPanel.md +9 -0
- package/dist/okf/types/TextboxVariable.md +9 -0
- package/dist/okf/types/TimeSeriesPanel.md +9 -0
- package/dist/okf/types/TracesPanel.md +9 -0
- package/dist/package-cli.d.ts +3 -0
- package/dist/package-cli.d.ts.map +1 -0
- package/dist/panels.d.ts +138 -0
- package/dist/panels.d.ts.map +1 -0
- package/dist/pin.d.ts +32 -0
- package/dist/pin.d.ts.map +1 -0
- package/dist/plugin.d.ts +12 -0
- package/dist/plugin.d.ts.map +1 -0
- package/dist/query.d.ts +81 -0
- package/dist/query.d.ts.map +1 -0
- package/dist/rules/graf101.ts +17 -0
- package/dist/rules/graf102.ts +17 -0
- package/dist/rules/graf103.ts +17 -0
- package/dist/rules/graf104.ts +17 -0
- package/dist/rules/graf105.ts +17 -0
- package/dist/rules/graf106.ts +17 -0
- package/dist/rules/graf107.ts +17 -0
- package/dist/rules/grafana-ast.ts +70 -0
- package/dist/rules/grafana-helpers.ts +68 -0
- package/dist/rules/literal-secret.ts +48 -0
- package/dist/rules/uid-syntax.ts +56 -0
- package/dist/schema/dashboard.gen.d.ts +1231 -0
- package/dist/schema/dashboard.gen.d.ts.map +1 -0
- package/dist/schema/gauge.gen.d.ts +57 -0
- package/dist/schema/gauge.gen.d.ts.map +1 -0
- package/dist/schema/heatmap.gen.d.ts +295 -0
- package/dist/schema/heatmap.gen.d.ts.map +1 -0
- package/dist/schema/index.d.ts +18 -0
- package/dist/schema/index.d.ts.map +1 -0
- package/dist/schema/logs.gen.d.ts +29 -0
- package/dist/schema/logs.gen.d.ts.map +1 -0
- package/dist/schema/loki.gen.d.ts +69 -0
- package/dist/schema/loki.gen.d.ts.map +1 -0
- package/dist/schema/prometheus.gen.d.ts +169 -0
- package/dist/schema/prometheus.gen.d.ts.map +1 -0
- package/dist/schema/stat.gen.d.ts +75 -0
- package/dist/schema/stat.gen.d.ts.map +1 -0
- package/dist/schema/table.gen.d.ts +295 -0
- package/dist/schema/table.gen.d.ts.map +1 -0
- package/dist/schema/tempo.gen.d.ts +136 -0
- package/dist/schema/tempo.gen.d.ts.map +1 -0
- package/dist/schema/text.gen.d.ts +16 -0
- package/dist/schema/text.gen.d.ts.map +1 -0
- package/dist/schema/timeseries.gen.d.ts +179 -0
- package/dist/schema/timeseries.gen.d.ts.map +1 -0
- package/dist/schema-validate.d.ts +20 -0
- package/dist/schema-validate.d.ts.map +1 -0
- package/dist/serializer.d.ts +22 -0
- package/dist/serializer.d.ts.map +1 -0
- package/dist/skill-defs.d.ts +3 -0
- package/dist/skill-defs.d.ts.map +1 -0
- package/dist/skills/chant-grafana-provisioning.md +42 -0
- package/dist/skills/chant-grafana.md +93 -0
- package/dist/spec/fetch-cli.d.ts +3 -0
- package/dist/spec/fetch-cli.d.ts.map +1 -0
- package/dist/spec/schemas.d.ts +25 -0
- package/dist/spec/schemas.d.ts.map +1 -0
- package/dist/types/index.d.ts +3 -0
- package/dist/util.d.ts +17 -0
- package/dist/util.d.ts.map +1 -0
- package/dist/validate-cli.d.ts +3 -0
- package/dist/validate-cli.d.ts.map +1 -0
- package/dist/validate-output.d.ts +42 -0
- package/dist/validate-output.d.ts.map +1 -0
- package/dist/validate.d.ts +12 -0
- package/dist/validate.d.ts.map +1 -0
- package/dist/variables.d.ts +100 -0
- package/dist/variables.d.ts.map +1 -0
- package/package.json +79 -0
- package/src/build.test.ts +210 -0
- package/src/build.ts +573 -0
- package/src/catalog.ts +116 -0
- package/src/codegen/docs-cli.ts +4 -0
- package/src/codegen/docs.ts +97 -0
- package/src/codegen/generate-cli.ts +8 -0
- package/src/codegen/generate.ts +83 -0
- package/src/codegen/package.ts +41 -0
- package/src/codegen/schema-types.ts +173 -0
- package/src/composites/agent-dashboard.ts +235 -0
- package/src/composites/catalog.test.ts +37 -0
- package/src/composites/catalog.ts +223 -0
- package/src/composites/composites.test.ts +314 -0
- package/src/composites/index.ts +12 -0
- package/src/composites/red-dashboard.ts +159 -0
- package/src/composites/shared.ts +94 -0
- package/src/composites/slo-dashboard.ts +177 -0
- package/src/dashboard.ts +123 -0
- package/src/datasource.ts +93 -0
- package/src/detect.ts +30 -0
- package/src/generated/lexicon-grafana.json +107 -0
- package/src/import.e2e.test.ts +147 -0
- package/src/import.test.ts +45 -0
- package/src/index.ts +178 -0
- package/src/lint/audit-catalog.ts +87 -0
- package/src/lint/post-synth/graf101.ts +17 -0
- package/src/lint/post-synth/graf102.ts +17 -0
- package/src/lint/post-synth/graf103.ts +17 -0
- package/src/lint/post-synth/graf104.ts +17 -0
- package/src/lint/post-synth/graf105.ts +17 -0
- package/src/lint/post-synth/graf106.ts +17 -0
- package/src/lint/post-synth/graf107.ts +17 -0
- package/src/lint/post-synth/grafana-helpers.ts +68 -0
- package/src/lint/post-synth/index.ts +19 -0
- package/src/lint/post-synth/post-synth.test.ts +231 -0
- package/src/lint/rules/grafana-ast.ts +70 -0
- package/src/lint/rules/index.ts +9 -0
- package/src/lint/rules/literal-secret.ts +48 -0
- package/src/lint/rules/rules.test.ts +70 -0
- package/src/lint/rules/uid-syntax.ts +56 -0
- package/src/lsp/completions.test.ts +31 -0
- package/src/lsp/completions.ts +15 -0
- package/src/lsp/hover.test.ts +25 -0
- package/src/lsp/hover.ts +28 -0
- package/src/package-cli.ts +17 -0
- package/src/panels.ts +275 -0
- package/src/pin.ts +68 -0
- package/src/plugin.test.ts +50 -0
- package/src/plugin.ts +116 -0
- package/src/query.ts +160 -0
- package/src/schema/dashboard.gen.ts +1289 -0
- package/src/schema/gauge.gen.ts +64 -0
- package/src/schema/heatmap.gen.ts +321 -0
- package/src/schema/index.ts +17 -0
- package/src/schema/logs.gen.ts +34 -0
- package/src/schema/loki.gen.ts +77 -0
- package/src/schema/prometheus.gen.ts +181 -0
- package/src/schema/stat.gen.ts +86 -0
- package/src/schema/table.gen.ts +334 -0
- package/src/schema/tempo.gen.ts +146 -0
- package/src/schema/text.gen.ts +22 -0
- package/src/schema/timeseries.gen.ts +208 -0
- package/src/schema-validate.ts +143 -0
- package/src/schema.test.ts +76 -0
- package/src/serializer.test.ts +170 -0
- package/src/serializer.ts +37 -0
- package/src/skill-defs.ts +31 -0
- package/src/skills/chant-grafana-provisioning.md +42 -0
- package/src/skills/chant-grafana.md +93 -0
- package/src/spec/fetch-cli.ts +27 -0
- package/src/spec/schemas/dashboard.jsonschema.json +1901 -0
- package/src/spec/schemas/gauge.jsonschema.json +114 -0
- package/src/spec/schemas/heatmap.jsonschema.json +480 -0
- package/src/spec/schemas/logs.jsonschema.json +94 -0
- package/src/spec/schemas/loki.jsonschema.json +118 -0
- package/src/spec/schemas/prometheus.jsonschema.json +276 -0
- package/src/spec/schemas/stat.jsonschema.json +163 -0
- package/src/spec/schemas/table.jsonschema.json +751 -0
- package/src/spec/schemas/tempo.jsonschema.json +225 -0
- package/src/spec/schemas/text.jsonschema.json +72 -0
- package/src/spec/schemas/timeseries.jsonschema.json +471 -0
- package/src/spec/schemas.ts +56 -0
- package/src/util.ts +43 -0
- package/src/validate-cli.ts +7 -0
- package/src/validate-output.ts +380 -0
- package/src/validate.ts +108 -0
- package/src/variables.ts +161 -0
package/dist/query.d.ts
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Typed queries, one class per datasource plugin: `PromQuery` (PromQL),
|
|
3
|
+
* `TempoQuery` (TraceQL) and `LokiQuery` (LogQL), each typed from that
|
|
4
|
+
* plugin's query schema at `GRAFANA_SCHEMA_PIN`.
|
|
5
|
+
*
|
|
6
|
+
* The expression is a string; this lexicon does not parse PromQL, TraceQL or
|
|
7
|
+
* LogQL. What it does check is where the query goes: `datasource` only
|
|
8
|
+
* accepts a datasource of the query's own plugin type, and GRAF101, GRAF102
|
|
9
|
+
* and GRAF103 check the emitted references after a build.
|
|
10
|
+
*
|
|
11
|
+
* `defineQuery` is the extension point for any other datasource plugin, and
|
|
12
|
+
* the three built-ins are defined through it.
|
|
13
|
+
*/
|
|
14
|
+
import type { Declarable } from "@intentius/chant/declarable";
|
|
15
|
+
import type { DatasourceEntity, DatasourceRef } from "./datasource.js";
|
|
16
|
+
import type { DatasourceVariableEntity } from "./variables.js";
|
|
17
|
+
import type { SchemaName } from "./pin.js";
|
|
18
|
+
import type { Dataquery as PrometheusDataquery } from "./schema/prometheus.gen.js";
|
|
19
|
+
import type { Dataquery as TempoDataquery } from "./schema/tempo.gen.js";
|
|
20
|
+
import type { Dataquery as LokiDataquery } from "./schema/loki.gen.js";
|
|
21
|
+
/** Where a query or panel sends its request: a declared datasource, a datasource variable, or the ref of one declared elsewhere. */
|
|
22
|
+
export type DatasourceInput<T extends string = string> = DatasourceEntity<T> | DatasourceVariableEntity<T> | DatasourceRef<T>;
|
|
23
|
+
/** A query model's fields, less what chant fills in (`datasource`, `refId` stays optional). */
|
|
24
|
+
export type QueryModel<M> = Omit<M, "datasource">;
|
|
25
|
+
export type QueryProps<M, T extends string> = QueryModel<M> & {
|
|
26
|
+
/** Overrides the panel's datasource for this query. */
|
|
27
|
+
datasource?: DatasourceInput<T>;
|
|
28
|
+
};
|
|
29
|
+
export interface QueryDefinition<T extends string = string, M = Record<string, unknown>> {
|
|
30
|
+
/** The datasource plugin id this query is for. */
|
|
31
|
+
datasourceType: T;
|
|
32
|
+
/** The class name, for hover and docs. */
|
|
33
|
+
className: string;
|
|
34
|
+
description?: string;
|
|
35
|
+
/** The vendored schema the model follows, when there is one (GRAF107 validates against it). */
|
|
36
|
+
schema?: SchemaName;
|
|
37
|
+
/** Which field holds the expression, for GRAF103 and hover. */
|
|
38
|
+
expressionField?: string;
|
|
39
|
+
/** Fields the schema requires that Grafana fills in anyway, written when the author leaves them out. */
|
|
40
|
+
defaults?: Partial<M>;
|
|
41
|
+
/** True only for the queries this package ships. */
|
|
42
|
+
builtin: boolean;
|
|
43
|
+
}
|
|
44
|
+
export interface QueryEntity<T extends string = string, M = Record<string, unknown>> extends Declarable {
|
|
45
|
+
readonly props: QueryProps<M, T>;
|
|
46
|
+
readonly queryDefinition: QueryDefinition<T, M>;
|
|
47
|
+
}
|
|
48
|
+
export interface QueryClass<T extends string = string, M = Record<string, unknown>> {
|
|
49
|
+
new (props: QueryProps<M, T>): QueryEntity<T, M>;
|
|
50
|
+
readonly definition: QueryDefinition<T, M>;
|
|
51
|
+
}
|
|
52
|
+
export declare const QUERY_TYPE_PREFIX = "Grafana::Query::";
|
|
53
|
+
/** Every registered query definition, keyed by datasource type. */
|
|
54
|
+
export declare function registeredQueries(): QueryDefinition[];
|
|
55
|
+
export declare function queryDefinitionFor(datasourceType: string): QueryDefinition | undefined;
|
|
56
|
+
/**
|
|
57
|
+
* Define a query class for a datasource plugin chant doesn't ship.
|
|
58
|
+
*
|
|
59
|
+
* @example
|
|
60
|
+
* ```ts
|
|
61
|
+
* interface ElasticQueryModel { query: string; metrics?: Array<{ type: string; id: string }>; refId?: string }
|
|
62
|
+
*
|
|
63
|
+
* export const ElasticQuery = defineQuery<ElasticQueryModel>()({
|
|
64
|
+
* datasourceType: "elasticsearch",
|
|
65
|
+
* className: "ElasticQuery",
|
|
66
|
+
* expressionField: "query",
|
|
67
|
+
* });
|
|
68
|
+
* ```
|
|
69
|
+
*/
|
|
70
|
+
export declare function defineQuery<M>(): <T extends string>(def: Omit<QueryDefinition<T, M>, "builtin">) => QueryClass<T, M>;
|
|
71
|
+
export declare function isQueryEntity(value: unknown): value is QueryEntity;
|
|
72
|
+
/** A Prometheus query: `expr` is PromQL. */
|
|
73
|
+
export declare const PromQuery: QueryClass<"prometheus", PrometheusDataquery>;
|
|
74
|
+
/** A Tempo query: `query` is TraceQL. `filters` defaults to none. */
|
|
75
|
+
export declare const TempoQuery: QueryClass<"tempo", Omit<TempoDataquery, "filters"> & Partial<Pick<TempoDataquery, "filters">>>;
|
|
76
|
+
/** A Loki query: `expr` is LogQL. */
|
|
77
|
+
export declare const LokiQuery: QueryClass<"loki", LokiDataquery>;
|
|
78
|
+
export type PromQueryEntity = InstanceType<typeof PromQuery>;
|
|
79
|
+
export type TempoQueryEntity = InstanceType<typeof TempoQuery>;
|
|
80
|
+
export type LokiQueryEntity = InstanceType<typeof LokiQuery>;
|
|
81
|
+
//# sourceMappingURL=query.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"query.d.ts","sourceRoot":"","sources":["../src/query.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GRAF101: A panel, query or variable uses a datasource no Datasource in the build declares
|
|
3
|
+
*
|
|
4
|
+
* Grafana shows "datasource not found" on the panel. Typed references cannot go wrong this way, so it mostly catches a { type, uid } ref or a hand-edited dashboard. A panel with queries and no datasource at all is a warning: Grafana sends them to its default datasource. This check joins dashboards against the Datasource declarations of the same build root, so it goes silent across build roots (chant #1939): with no Datasource declared in the build it reports nothing, and a datasource declared in another build root looks undeclared. Keep datasources and the dashboards that use them in one build root.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
|
|
8
|
+
import { grafanaDiagnostics } from "./grafana-helpers";
|
|
9
|
+
|
|
10
|
+
export const graf101: PostSynthCheck = {
|
|
11
|
+
id: "GRAF101",
|
|
12
|
+
description: "A panel, query or variable uses a datasource no Datasource in the build declares",
|
|
13
|
+
|
|
14
|
+
check(ctx: PostSynthContext): PostSynthDiagnostic[] {
|
|
15
|
+
return grafanaDiagnostics(ctx, "GRAF101");
|
|
16
|
+
},
|
|
17
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GRAF102: A query is sent to a datasource of another plugin type
|
|
3
|
+
*
|
|
4
|
+
* A PromQL query sent to Tempo fails at query time. The typed classes prevent this at compile time; the check catches refs, datasource variables of the wrong plugin type and hand-edited dashboards. Like GRAF101 it joins against the Datasource declarations of the same build root and goes silent across build roots (chant #1939).
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
|
|
8
|
+
import { grafanaDiagnostics } from "./grafana-helpers";
|
|
9
|
+
|
|
10
|
+
export const graf102: PostSynthCheck = {
|
|
11
|
+
id: "GRAF102",
|
|
12
|
+
description: "A query is sent to a datasource of another plugin type",
|
|
13
|
+
|
|
14
|
+
check(ctx: PostSynthContext): PostSynthDiagnostic[] {
|
|
15
|
+
return grafanaDiagnostics(ctx, "GRAF102");
|
|
16
|
+
},
|
|
17
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GRAF103: A query, title, datasource or repeat uses a variable the dashboard does not declare
|
|
3
|
+
*
|
|
4
|
+
* Grafana leaves an undeclared $name in the query as text, so the query fails or matches nothing. Names Grafana defines itself ($__interval, $__rate_interval, $__range and the rest of $__*) need no declaration.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
|
|
8
|
+
import { grafanaDiagnostics } from "./grafana-helpers";
|
|
9
|
+
|
|
10
|
+
export const graf103: PostSynthCheck = {
|
|
11
|
+
id: "GRAF103",
|
|
12
|
+
description: "A query, title, datasource or repeat uses a variable the dashboard does not declare",
|
|
13
|
+
|
|
14
|
+
check(ctx: PostSynthContext): PostSynthDiagnostic[] {
|
|
15
|
+
return grafanaDiagnostics(ctx, "GRAF103");
|
|
16
|
+
},
|
|
17
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GRAF104: Two dashboards, datasources, panels, variables or queries share an id
|
|
3
|
+
*
|
|
4
|
+
* Dashboard uids and datasource uids and names must be unique across the build, panel ids and variable names within a dashboard, and refIds within a panel. Grafana keeps one of two dashboards with the same uid and silently drops the other.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
|
|
8
|
+
import { grafanaDiagnostics } from "./grafana-helpers";
|
|
9
|
+
|
|
10
|
+
export const graf104: PostSynthCheck = {
|
|
11
|
+
id: "GRAF104",
|
|
12
|
+
description: "Two dashboards, datasources, panels, variables or queries share an id",
|
|
13
|
+
|
|
14
|
+
check(ctx: PostSynthContext): PostSynthDiagnostic[] {
|
|
15
|
+
return grafanaDiagnostics(ctx, "GRAF104");
|
|
16
|
+
},
|
|
17
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GRAF105: A panel does not fit the 24-column grid, or overlaps another panel
|
|
3
|
+
*
|
|
4
|
+
* A panel wider than the grid is an error. An overlap is a warning: Grafana moves one of the panels on load, so the dashboard does not look the way it was declared. Panels without x and y are placed by the dashboard and cannot overlap each other.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
|
|
8
|
+
import { grafanaDiagnostics } from "./grafana-helpers";
|
|
9
|
+
|
|
10
|
+
export const graf105: PostSynthCheck = {
|
|
11
|
+
id: "GRAF105",
|
|
12
|
+
description: "A panel does not fit the 24-column grid, or overlaps another panel",
|
|
13
|
+
|
|
14
|
+
check(ctx: PostSynthContext): PostSynthDiagnostic[] {
|
|
15
|
+
return grafanaDiagnostics(ctx, "GRAF105");
|
|
16
|
+
},
|
|
17
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GRAF106: A dashboard or datasource uid Grafana rejects, or a dashboard with no title
|
|
3
|
+
*
|
|
4
|
+
* Grafana accepts uids of 1-40 letters, digits, - and _, and refuses to save a dashboard without a title.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
|
|
8
|
+
import { grafanaDiagnostics } from "./grafana-helpers";
|
|
9
|
+
|
|
10
|
+
export const graf106: PostSynthCheck = {
|
|
11
|
+
id: "GRAF106",
|
|
12
|
+
description: "A dashboard or datasource uid Grafana rejects, or a dashboard with no title",
|
|
13
|
+
|
|
14
|
+
check(ctx: PostSynthContext): PostSynthDiagnostic[] {
|
|
15
|
+
return grafanaDiagnostics(ctx, "GRAF106");
|
|
16
|
+
},
|
|
17
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GRAF107: A dashboard does not match the pinned Grafana schema
|
|
3
|
+
*
|
|
4
|
+
* The dashboard is validated against the dashboard schema at GRAFANA_SCHEMA_PIN, and each panel's options, fieldConfig.defaults.custom and queries against their plugin's schema with required fields relaxed (Grafana fills those in). What fails is a key the schema does not have or a value it does not allow: the edits Grafana would otherwise need on import.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
|
|
8
|
+
import { grafanaDiagnostics } from "./grafana-helpers";
|
|
9
|
+
|
|
10
|
+
export const graf107: PostSynthCheck = {
|
|
11
|
+
id: "GRAF107",
|
|
12
|
+
description: "A dashboard does not match the pinned Grafana schema",
|
|
13
|
+
|
|
14
|
+
check(ctx: PostSynthContext): PostSynthDiagnostic[] {
|
|
15
|
+
return grafanaDiagnostics(ctx, "GRAF107");
|
|
16
|
+
},
|
|
17
|
+
};
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import * as ts from "typescript";
|
|
2
|
+
|
|
3
|
+
/** The constructor name of a `new X(...)` or `X(...)` expression, if it is a plain or dotted name. */
|
|
4
|
+
export function calleeName(node: ts.CallExpression | ts.NewExpression): string | undefined {
|
|
5
|
+
const callee = node.expression;
|
|
6
|
+
if (ts.isIdentifier(callee)) return callee.text;
|
|
7
|
+
if (ts.isPropertyAccessExpression(callee)) return callee.name.text;
|
|
8
|
+
return undefined;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/** A property's key as text, for identifier and string-literal keys. */
|
|
12
|
+
export function propertyName(prop: ts.ObjectLiteralElementLike): string | undefined {
|
|
13
|
+
if (!ts.isPropertyAssignment(prop)) return undefined;
|
|
14
|
+
if (ts.isIdentifier(prop.name) || ts.isStringLiteral(prop.name)) return prop.name.text;
|
|
15
|
+
return undefined;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** The text of a string literal or substitution-free template, else undefined. */
|
|
19
|
+
export function literalText(node: ts.Expression): string | undefined {
|
|
20
|
+
if (ts.isStringLiteral(node) || ts.isNoSubstitutionTemplateLiteral(node)) return node.text;
|
|
21
|
+
return undefined;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function position(source: ts.SourceFile, node: ts.Node): { line: number; column: number } {
|
|
25
|
+
const { line, character } = source.getLineAndCharacterOfPosition(node.getStart(source));
|
|
26
|
+
return { line: line + 1, column: character + 1 };
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** `const name = <init>` declarations in the file, so a value written as a named const can be followed. */
|
|
30
|
+
export function constInitializers(source: ts.SourceFile): Map<string, ts.Expression> {
|
|
31
|
+
const out = new Map<string, ts.Expression>();
|
|
32
|
+
const visit = (node: ts.Node) => {
|
|
33
|
+
if (ts.isVariableDeclaration(node) && ts.isIdentifier(node.name) && node.initializer) out.set(node.name.text, node.initializer);
|
|
34
|
+
ts.forEachChild(node, visit);
|
|
35
|
+
};
|
|
36
|
+
visit(source);
|
|
37
|
+
return out;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Follow an identifier to the object literal it was declared as, when it is one in this file. */
|
|
41
|
+
export function resolveObject(node: ts.Expression, consts: Map<string, ts.Expression>, depth = 0): ts.ObjectLiteralExpression | undefined {
|
|
42
|
+
let n: ts.Expression = node;
|
|
43
|
+
while (ts.isAsExpression(n) || ts.isSatisfiesExpression(n) || ts.isParenthesizedExpression(n)) n = n.expression;
|
|
44
|
+
if (ts.isObjectLiteralExpression(n)) return n;
|
|
45
|
+
if (ts.isIdentifier(n) && depth < 5) {
|
|
46
|
+
const init = consts.get(n.text);
|
|
47
|
+
if (init) return resolveObject(init, consts, depth + 1);
|
|
48
|
+
}
|
|
49
|
+
return undefined;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** The first object-literal argument of every `new <Name>(...)` or `<Name>(...)` for the names given. */
|
|
53
|
+
export function constructorArgs(
|
|
54
|
+
source: ts.SourceFile,
|
|
55
|
+
names: (name: string) => boolean,
|
|
56
|
+
): Array<{ name: string; arg: ts.ObjectLiteralExpression }> {
|
|
57
|
+
const consts = constInitializers(source);
|
|
58
|
+
const out: Array<{ name: string; arg: ts.ObjectLiteralExpression }> = [];
|
|
59
|
+
const visit = (node: ts.Node) => {
|
|
60
|
+
if (ts.isNewExpression(node) || ts.isCallExpression(node)) {
|
|
61
|
+
const name = calleeName(node);
|
|
62
|
+
const first = node.arguments?.[0];
|
|
63
|
+
const arg = first ? resolveObject(first, consts) : undefined;
|
|
64
|
+
if (name && arg && names(name)) out.push({ name, arg });
|
|
65
|
+
}
|
|
66
|
+
ts.forEachChild(node, visit);
|
|
67
|
+
};
|
|
68
|
+
visit(source);
|
|
69
|
+
return out;
|
|
70
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared plumbing for the grafana post-synth checks: find the dashboards and
|
|
3
|
+
* provisioned datasources in a build's output, and run the plain-function
|
|
4
|
+
* checks in `validate-output.ts` over them.
|
|
5
|
+
*
|
|
6
|
+
* Any output file shaped like a dashboard or a datasource provisioning file
|
|
7
|
+
* counts, not only the grafana lexicon's own, so a dashboard another lexicon
|
|
8
|
+
* embeds is checked the same way.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import type { PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
|
|
12
|
+
import type { SerializerResult } from "@intentius/chant/serializer";
|
|
13
|
+
import { loadAll } from "js-yaml";
|
|
14
|
+
import { looksLikeDashboard, looksLikeDatasourceProvisioning } from "../../detect";
|
|
15
|
+
import type { ProvisionedDatasource } from "../../build";
|
|
16
|
+
import { issuesFor, type GrafanaArtifacts, type GrafanaIssueCode } from "../../validate-output";
|
|
17
|
+
|
|
18
|
+
function parse(text: string, name: string): unknown[] {
|
|
19
|
+
if (name.endsWith(".json") || /^\s*[{[]/.test(text)) {
|
|
20
|
+
try {
|
|
21
|
+
return [JSON.parse(text)];
|
|
22
|
+
} catch {
|
|
23
|
+
// fall through to YAML
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
try {
|
|
27
|
+
return loadAll(text);
|
|
28
|
+
} catch {
|
|
29
|
+
return [];
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Every dashboard and provisioned datasource in the build's output. */
|
|
34
|
+
export function grafanaArtifacts(ctx: PostSynthContext): GrafanaArtifacts {
|
|
35
|
+
const out: GrafanaArtifacts = { dashboards: [], datasources: [] };
|
|
36
|
+
for (const [lexicon, output] of ctx.outputs) {
|
|
37
|
+
const texts: Array<[string, string]> =
|
|
38
|
+
typeof output === "string"
|
|
39
|
+
? [[lexicon, output]]
|
|
40
|
+
: [[lexicon, (output as SerializerResult).primary], ...Object.entries((output as SerializerResult).files ?? {})];
|
|
41
|
+
for (const [source, text] of texts) {
|
|
42
|
+
if (!text) continue;
|
|
43
|
+
for (const doc of parse(text, source)) {
|
|
44
|
+
if (looksLikeDashboard(doc)) out.dashboards.push({ source, json: doc });
|
|
45
|
+
else if (looksLikeDatasourceProvisioning(doc)) {
|
|
46
|
+
for (const d of doc.datasources) {
|
|
47
|
+
if (d && typeof d === "object" && typeof (d as ProvisionedDatasource).name === "string") {
|
|
48
|
+
const ds = d as ProvisionedDatasource;
|
|
49
|
+
out.datasources.push({ ...ds, uid: typeof ds.uid === "string" ? ds.uid : ds.name });
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return out;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** Diagnostics for one check across the build's output. */
|
|
60
|
+
export function grafanaDiagnostics(ctx: PostSynthContext, code: GrafanaIssueCode): PostSynthDiagnostic[] {
|
|
61
|
+
return issuesFor(code, grafanaArtifacts(ctx)).map((i) => ({
|
|
62
|
+
checkId: i.code,
|
|
63
|
+
severity: i.severity,
|
|
64
|
+
message: i.message,
|
|
65
|
+
...(i.entity ? { entity: i.entity } : {}),
|
|
66
|
+
lexicon: "grafana",
|
|
67
|
+
}));
|
|
68
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import * as ts from "typescript";
|
|
2
|
+
import type { LintRule, LintDiagnostic, LintContext } from "@intentius/chant/lint/rule";
|
|
3
|
+
import { constInitializers, constructorArgs, literalText, position, propertyName, resolveObject } from "./grafana-ast";
|
|
4
|
+
|
|
5
|
+
/** How Grafana provisioning reads a value from outside the file: `$VAR`, `${VAR}`, `$__env{…}`, `$__file{…}`, `$__vault{…}`. */
|
|
6
|
+
const EXPANDED = /\$(\{[A-Za-z_][A-Za-z0-9_]*\}|[A-Za-z_][A-Za-z0-9_]*|__(env|file|vault)\{[^}]+\})/;
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* GRAF002: a datasource secret written as a literal.
|
|
10
|
+
*
|
|
11
|
+
* `secureJsonData` holds passwords, tokens and TLS keys. A literal there is a
|
|
12
|
+
* secret in the repository and in the emitted provisioning file. Grafana
|
|
13
|
+
* expands `$__env{NAME}`, `$__file{/path}` and `${NAME}` when it reads the
|
|
14
|
+
* file, so the declaration never needs the value. A value written as a
|
|
15
|
+
* named `const` in the same file is followed.
|
|
16
|
+
*/
|
|
17
|
+
export const literalSecretRule: LintRule = {
|
|
18
|
+
id: "GRAF002",
|
|
19
|
+
severity: "error",
|
|
20
|
+
category: "security",
|
|
21
|
+
description: "Datasource secureJsonData value declared as a literal instead of $__env{...} or $__file{...}",
|
|
22
|
+
|
|
23
|
+
check(context: LintContext): LintDiagnostic[] {
|
|
24
|
+
const diagnostics: LintDiagnostic[] = [];
|
|
25
|
+
const source = context.sourceFile;
|
|
26
|
+
const consts = constInitializers(source);
|
|
27
|
+
for (const { arg } of constructorArgs(source, (n) => n === "Datasource")) {
|
|
28
|
+
for (const prop of arg.properties) {
|
|
29
|
+
if (propertyName(prop) !== "secureJsonData") continue;
|
|
30
|
+
const secure = resolveObject((prop as ts.PropertyAssignment).initializer, consts);
|
|
31
|
+
if (!secure) continue;
|
|
32
|
+
for (const entry of secure.properties) {
|
|
33
|
+
if (!ts.isPropertyAssignment(entry)) continue;
|
|
34
|
+
const text = literalText(entry.initializer);
|
|
35
|
+
if (text === undefined || text === "" || EXPANDED.test(text)) continue;
|
|
36
|
+
diagnostics.push({
|
|
37
|
+
ruleId: "GRAF002",
|
|
38
|
+
severity: "error",
|
|
39
|
+
message: `secureJsonData.${propertyName(entry) ?? "?"} is a literal secret; write "$__env{NAME}" or "$__file{/path}" and let Grafana read it`,
|
|
40
|
+
file: context.filePath,
|
|
41
|
+
...position(source, entry.initializer),
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return diagnostics;
|
|
47
|
+
},
|
|
48
|
+
};
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import type * as ts from "typescript";
|
|
2
|
+
import type { LintRule, LintDiagnostic, LintContext } from "@intentius/chant/lint/rule";
|
|
3
|
+
import { UID_PATTERN } from "../../util";
|
|
4
|
+
import { VARIABLE_NAME } from "../../variables";
|
|
5
|
+
import { constructorArgs, literalText, position, propertyName } from "./grafana-ast";
|
|
6
|
+
|
|
7
|
+
const UID_OWNERS = new Set(["Dashboard", "Datasource"]);
|
|
8
|
+
const VARIABLE_CLASS = /Variable$/;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* GRAF001: a uid Grafana rejects, or a variable name queries cannot reference.
|
|
12
|
+
*
|
|
13
|
+
* A literal `uid` on a `Dashboard` or `Datasource` must be 1-40 letters,
|
|
14
|
+
* digits, `-` and `_`; Grafana refuses anything else on import. A literal
|
|
15
|
+
* `name` on a `*Variable` must be letters, digits and `_`, starting with a
|
|
16
|
+
* letter or `_`, or `$name` in a query cannot refer to it. Caught in source,
|
|
17
|
+
* before a build; GRAF106 checks the emitted uids too.
|
|
18
|
+
*/
|
|
19
|
+
export const uidSyntaxRule: LintRule = {
|
|
20
|
+
id: "GRAF001",
|
|
21
|
+
severity: "error",
|
|
22
|
+
category: "correctness",
|
|
23
|
+
description: "Dashboard or datasource uid Grafana rejects, or a variable name queries can't reference",
|
|
24
|
+
|
|
25
|
+
check(context: LintContext): LintDiagnostic[] {
|
|
26
|
+
const diagnostics: LintDiagnostic[] = [];
|
|
27
|
+
const source = context.sourceFile;
|
|
28
|
+
for (const { name, arg } of constructorArgs(source, (n) => UID_OWNERS.has(n) || VARIABLE_CLASS.test(n))) {
|
|
29
|
+
for (const prop of arg.properties) {
|
|
30
|
+
const key = propertyName(prop);
|
|
31
|
+
if (!key) continue;
|
|
32
|
+
const text = literalText((prop as ts.PropertyAssignment).initializer);
|
|
33
|
+
if (text === undefined) continue;
|
|
34
|
+
if (UID_OWNERS.has(name) && key === "uid" && !UID_PATTERN.test(text)) {
|
|
35
|
+
diagnostics.push({
|
|
36
|
+
ruleId: "GRAF001",
|
|
37
|
+
severity: "error",
|
|
38
|
+
message: `${name} uid "${text}" is not a Grafana uid; use 1-40 letters, digits, "-" and "_"`,
|
|
39
|
+
file: context.filePath,
|
|
40
|
+
...position(source, prop),
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
if (VARIABLE_CLASS.test(name) && key === "name" && !VARIABLE_NAME.test(text)) {
|
|
44
|
+
diagnostics.push({
|
|
45
|
+
ruleId: "GRAF001",
|
|
46
|
+
severity: "error",
|
|
47
|
+
message: `variable name "${text}" can't be referenced as $name; use letters, digits and "_", not starting with a digit`,
|
|
48
|
+
file: context.filePath,
|
|
49
|
+
...position(source, prop),
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return diagnostics;
|
|
55
|
+
},
|
|
56
|
+
};
|