@intentius/chant-lexicon-prometheus 0.105.1 → 0.106.1
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 +5 -2
- package/dist/build.d.ts +17 -0
- package/dist/build.d.ts.map +1 -1
- package/dist/catalog.d.ts +1 -1
- package/dist/catalog.d.ts.map +1 -1
- package/dist/composites/slo.d.ts +10 -0
- package/dist/composites/slo.d.ts.map +1 -1
- package/dist/config-model.d.ts +272 -0
- package/dist/config-model.d.ts.map +1 -0
- package/dist/detect.d.ts.map +1 -1
- package/dist/import/generator.d.ts +3 -1
- package/dist/import/generator.d.ts.map +1 -1
- package/dist/import/parser.d.ts +12 -2
- package/dist/import/parser.d.ts.map +1 -1
- package/dist/import/slo.d.ts.map +1 -1
- package/dist/index.d.ts +4 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/integrity.json +5 -4
- package/dist/lint/audit-catalog.d.ts.map +1 -1
- package/dist/lint/audit-lineage.d.ts +2 -0
- package/dist/lint/audit-lineage.d.ts.map +1 -1
- package/dist/lint/post-synth/index.d.ts.map +1 -1
- package/dist/lint/post-synth/prom-helpers.d.ts +7 -0
- package/dist/lint/post-synth/prom-helpers.d.ts.map +1 -1
- package/dist/lint/post-synth/prom225.d.ts +10 -0
- package/dist/lint/post-synth/prom225.d.ts.map +1 -0
- package/dist/manifest.json +1 -1
- package/dist/meta.json +10 -0
- package/dist/model.d.ts +1 -0
- package/dist/model.d.ts.map +1 -1
- package/dist/okf/index.md +3 -0
- package/dist/okf/rules/PROM225.md +11 -0
- package/dist/okf/types/PrometheusConfig.md +9 -0
- package/dist/okf/types/ScrapeConfig.md +9 -0
- package/dist/plugin.d.ts.map +1 -1
- package/dist/prometheus-config.d.ts +39 -0
- package/dist/prometheus-config.d.ts.map +1 -0
- package/dist/rules/prom-helpers.ts +10 -1
- package/dist/rules/prom225.ts +19 -0
- package/dist/serializer.d.ts +14 -10
- package/dist/serializer.d.ts.map +1 -1
- package/dist/tools.d.ts +9 -1
- package/dist/tools.d.ts.map +1 -1
- package/dist/validate-config.d.ts +1 -1
- package/dist/validate-config.d.ts.map +1 -1
- package/dist/validate-relabel.d.ts +14 -0
- package/dist/validate-relabel.d.ts.map +1 -0
- package/dist/validate.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/build.ts +73 -0
- package/src/catalog.ts +14 -1
- package/src/composites/composites.test.ts +43 -1
- package/src/composites/slo.ts +22 -4
- package/src/config-model.ts +327 -0
- package/src/detect.ts +5 -4
- package/src/generated/lexicon-prometheus.json +10 -0
- package/src/import/generator.ts +120 -0
- package/src/import/parser.test.ts +2 -2
- package/src/import/parser.ts +72 -5
- package/src/import/prometheus-roundtrip.test.ts +148 -0
- package/src/import/slo.ts +15 -0
- package/src/import/testdata/prometheus-full.yml +149 -0
- package/src/index.ts +20 -2
- package/src/lint/audit-catalog.ts +7 -0
- package/src/lint/audit-lineage.test.ts +1 -1
- package/src/lint/audit-lineage.ts +2 -0
- package/src/lint/post-synth/checks.test.ts +34 -0
- package/src/lint/post-synth/index.ts +2 -0
- package/src/lint/post-synth/prom-helpers.ts +10 -1
- package/src/lint/post-synth/prom225.ts +19 -0
- package/src/model.ts +1 -0
- package/src/plugin.test.ts +1 -0
- package/src/plugin.ts +1 -0
- package/src/prometheus-config.test.ts +230 -0
- package/src/prometheus-config.ts +71 -0
- package/src/serializer.ts +31 -21
- package/src/tools.ts +31 -3
- package/src/validate-config.ts +2 -1
- package/src/validate-relabel.ts +53 -0
- package/src/validate.ts +1 -1
package/src/composites/slo.ts
CHANGED
|
@@ -14,6 +14,16 @@
|
|
|
14
14
|
* pair's factor: the long window keeps a short spike from paging, the
|
|
15
15
|
* short window stops the alert soon after the burn does.
|
|
16
16
|
*
|
|
17
|
+
* An SLI that sees no events in a window (a sparse one: a job that runs a few
|
|
18
|
+
* times a day) records no ratio for that window, where good/total would be
|
|
19
|
+
* 0/0 and record NaN. The SLO window's ratio is an average of the shortest
|
|
20
|
+
* recorded ratio, and one NaN in a range keeps `avg_over_time` NaN for the
|
|
21
|
+
* whole window, so the idle windows are left out and the average covers only
|
|
22
|
+
* the windows that had events. The SLI expressions are the caller's: `rate()`
|
|
23
|
+
* over a counter that first appears inside the range sees one sample and
|
|
24
|
+
* counts nothing, so a sparse SLI should count events as an increase that
|
|
25
|
+
* includes a series' first sample (see the SLOs page).
|
|
26
|
+
*
|
|
17
27
|
* Both kinds share a group because Prometheus evaluates a group's rules in
|
|
18
28
|
* order: the alerts read the ratios recorded in the same evaluation. In
|
|
19
29
|
* separate groups they would read the previous evaluation's, and with an
|
|
@@ -379,10 +389,18 @@ function metricsOf(r: Resolved): SloMetrics {
|
|
|
379
389
|
function recordingRules(r: Resolved, m: SloMetrics): RecordingRule[] {
|
|
380
390
|
const { props } = r;
|
|
381
391
|
const sli = props.sli as { good?: string; errors?: string; total: string };
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
392
|
+
// A window with no events records nothing: `total > 0` drops the idle
|
|
393
|
+
// window's 0/0 NaN, and the SLO window's average then covers only the
|
|
394
|
+
// windows that had events. The numerator falls back to `0 * total`, so a
|
|
395
|
+
// window whose events were all good (or all bad) still records 0 (or 1)
|
|
396
|
+
// where its own series is absent.
|
|
397
|
+
const ratio = (w: string) => {
|
|
398
|
+
const total = `(${withWindow(sli.total, w)}) > 0`;
|
|
399
|
+
const some = (expr: string) => `((${expr}) or 0 * (${total}))`;
|
|
400
|
+
return sli.errors !== undefined
|
|
401
|
+
? `${some(withWindow(sli.errors, w))}\n/\n(${total})`
|
|
402
|
+
: `1 - (\n ${some(withWindow(sli.good!, w))}\n /\n (${total})\n)`;
|
|
403
|
+
};
|
|
386
404
|
const labels = { slo: props.name };
|
|
387
405
|
const rules: RecordingRule[] = [];
|
|
388
406
|
const alertWindows = m.windows.slice(0, -1);
|
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The plain-data model of `prometheus.yml`: global settings, scrape configs,
|
|
3
|
+
* relabelling, service discovery, Alertmanager targets and remote storage.
|
|
4
|
+
*
|
|
5
|
+
* Written by hand against Prometheus v3.15.0 (`config/config.go` and
|
|
6
|
+
* `discovery/*`), field names in the file's own snake_case. SchemaStore's
|
|
7
|
+
* `prometheus.json` is not used to generate these: it is strict about unknown
|
|
8
|
+
* keys and behind v3.15.0 (no `runtime`, `otlp` or `tracing`, 11 of about 20
|
|
9
|
+
* `global` keys, 24 of 32 discovery kinds), so types generated from it would
|
|
10
|
+
* reject configs Prometheus accepts.
|
|
11
|
+
*
|
|
12
|
+
* Six discovery kinds are typed: kubernetes, file, http, dns, ec2 and consul.
|
|
13
|
+
* Any other `<kind>_sd_configs` key is carried as untyped data.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import type { HttpClientConfig } from "./integrations";
|
|
17
|
+
import type { LabelSet } from "./model";
|
|
18
|
+
|
|
19
|
+
/** One `static_configs` entry: fixed targets and the labels they share. */
|
|
20
|
+
export interface StaticConfig {
|
|
21
|
+
/** `host:port` addresses to scrape (or, under `alerting`, to send alerts to). */
|
|
22
|
+
targets: string[];
|
|
23
|
+
labels?: LabelSet;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** The actions a relabel step can take. */
|
|
27
|
+
export type RelabelAction =
|
|
28
|
+
| "replace"
|
|
29
|
+
| "lowercase"
|
|
30
|
+
| "uppercase"
|
|
31
|
+
| "keep"
|
|
32
|
+
| "drop"
|
|
33
|
+
| "keepequal"
|
|
34
|
+
| "dropequal"
|
|
35
|
+
| "hashmod"
|
|
36
|
+
| "labelmap"
|
|
37
|
+
| "labeldrop"
|
|
38
|
+
| "labelkeep";
|
|
39
|
+
|
|
40
|
+
/** One `relabel_configs` / `metric_relabel_configs` / `write_relabel_configs` step. */
|
|
41
|
+
export interface RelabelConfig {
|
|
42
|
+
/** Labels whose values are joined with `separator` and matched against `regex`. */
|
|
43
|
+
source_labels?: string[];
|
|
44
|
+
/** Defaults to `;`. */
|
|
45
|
+
separator?: string;
|
|
46
|
+
target_label?: string;
|
|
47
|
+
/** An RE2 regex, anchored. Defaults to `(.*)`. */
|
|
48
|
+
regex?: string;
|
|
49
|
+
modulus?: number;
|
|
50
|
+
/** Defaults to `$1`. */
|
|
51
|
+
replacement?: string;
|
|
52
|
+
/** Defaults to `replace`. */
|
|
53
|
+
action?: RelabelAction;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// ── Service discovery ───────────────────────────────────────────────
|
|
57
|
+
|
|
58
|
+
/** `kubernetes_sd_configs` entry. */
|
|
59
|
+
export interface KubernetesSDConfig extends HttpClientConfig {
|
|
60
|
+
role: "pod" | "service" | "endpoints" | "endpointslice" | "node" | "ingress";
|
|
61
|
+
kubeconfig_file?: string;
|
|
62
|
+
api_server?: string;
|
|
63
|
+
namespaces?: { own_namespace?: boolean; names?: string[] };
|
|
64
|
+
selectors?: Array<{ role: string; label?: string; field?: string }>;
|
|
65
|
+
attach_metadata?: { node?: boolean; namespace?: boolean };
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** `file_sd_configs` entry: target files Prometheus re-reads. */
|
|
69
|
+
export interface FileSDConfig {
|
|
70
|
+
/** Paths or globs of YAML and JSON target files. */
|
|
71
|
+
files: string[];
|
|
72
|
+
refresh_interval?: string;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** `http_sd_configs` entry: targets fetched from a URL. */
|
|
76
|
+
export interface HttpSDConfig extends HttpClientConfig {
|
|
77
|
+
url: string;
|
|
78
|
+
refresh_interval?: string;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** `dns_sd_configs` entry. */
|
|
82
|
+
export interface DnsSDConfig {
|
|
83
|
+
names: string[];
|
|
84
|
+
/** `SRV` (default), `A`, `AAAA`, `MX` or `NS`. */
|
|
85
|
+
type?: "SRV" | "A" | "AAAA" | "MX" | "NS";
|
|
86
|
+
/** Required for every type but `SRV`. */
|
|
87
|
+
port?: number;
|
|
88
|
+
refresh_interval?: string;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** `ec2_sd_configs` entry. */
|
|
92
|
+
export interface Ec2SDConfig extends HttpClientConfig {
|
|
93
|
+
region?: string;
|
|
94
|
+
endpoint?: string;
|
|
95
|
+
/** A credential: prefer the environment or an instance role. */
|
|
96
|
+
access_key?: string;
|
|
97
|
+
secret_key?: string;
|
|
98
|
+
profile?: string;
|
|
99
|
+
role_arn?: string;
|
|
100
|
+
refresh_interval?: string;
|
|
101
|
+
port?: number;
|
|
102
|
+
filters?: Array<{ name: string; values: string[] }>;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** `consul_sd_configs` entry. */
|
|
106
|
+
export interface ConsulSDConfig extends HttpClientConfig {
|
|
107
|
+
server?: string;
|
|
108
|
+
/** A credential. */
|
|
109
|
+
token?: string;
|
|
110
|
+
datacenter?: string;
|
|
111
|
+
namespace?: string;
|
|
112
|
+
partition?: string;
|
|
113
|
+
scheme?: "http" | "https";
|
|
114
|
+
username?: string;
|
|
115
|
+
password?: string;
|
|
116
|
+
allow_stale?: boolean;
|
|
117
|
+
services?: string[];
|
|
118
|
+
tags?: string[];
|
|
119
|
+
tag_separator?: string;
|
|
120
|
+
node_meta?: Record<string, string>;
|
|
121
|
+
refresh_interval?: string;
|
|
122
|
+
filter?: string;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/** The service discovery lists of a scrape config or an Alertmanager target group. */
|
|
126
|
+
export interface ServiceDiscoveryConfigs {
|
|
127
|
+
kubernetes_sd_configs?: KubernetesSDConfig[];
|
|
128
|
+
file_sd_configs?: FileSDConfig[];
|
|
129
|
+
http_sd_configs?: HttpSDConfig[];
|
|
130
|
+
dns_sd_configs?: DnsSDConfig[];
|
|
131
|
+
ec2_sd_configs?: Ec2SDConfig[];
|
|
132
|
+
consul_sd_configs?: ConsulSDConfig[];
|
|
133
|
+
/** Any other discovery kind (`azure_sd_configs`, `gce_sd_configs`, ...), carried as written. */
|
|
134
|
+
[other: `${string}_sd_configs`]: unknown[] | undefined;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/** The discovery kinds typed above. */
|
|
138
|
+
export const TYPED_SD_KINDS = Object.freeze([
|
|
139
|
+
"kubernetes_sd_configs",
|
|
140
|
+
"file_sd_configs",
|
|
141
|
+
"http_sd_configs",
|
|
142
|
+
"dns_sd_configs",
|
|
143
|
+
"ec2_sd_configs",
|
|
144
|
+
"consul_sd_configs",
|
|
145
|
+
]);
|
|
146
|
+
|
|
147
|
+
// ── Scrape configs ──────────────────────────────────────────────────
|
|
148
|
+
|
|
149
|
+
/** Limits and histogram settings that exist both in `global` and per scrape config. */
|
|
150
|
+
export interface ScrapeDefaults {
|
|
151
|
+
scrape_interval?: string;
|
|
152
|
+
scrape_timeout?: string;
|
|
153
|
+
/** e.g. `PrometheusProto`, `OpenMetricsText1.0.0`, `PrometheusText0.0.4`. */
|
|
154
|
+
scrape_protocols?: string[];
|
|
155
|
+
body_size_limit?: string;
|
|
156
|
+
sample_limit?: number;
|
|
157
|
+
label_limit?: number;
|
|
158
|
+
label_name_length_limit?: number;
|
|
159
|
+
label_value_length_limit?: number;
|
|
160
|
+
target_limit?: number;
|
|
161
|
+
keep_dropped_targets?: number;
|
|
162
|
+
always_scrape_classic_histograms?: boolean;
|
|
163
|
+
convert_classic_histograms_to_nhcb?: boolean;
|
|
164
|
+
metric_name_validation_scheme?: "utf8" | "legacy";
|
|
165
|
+
metric_name_escaping_scheme?: "allow-utf-8" | "underscores" | "dots" | "values";
|
|
166
|
+
native_histogram_bucket_limit?: number;
|
|
167
|
+
native_histogram_min_bucket_factor?: number;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/** One scrape job. */
|
|
171
|
+
export interface ScrapeJobConfig extends ScrapeDefaults, HttpClientConfig, ServiceDiscoveryConfigs {
|
|
172
|
+
/** Unique across the file; the `job` label on every target. */
|
|
173
|
+
job_name: string;
|
|
174
|
+
honor_labels?: boolean;
|
|
175
|
+
honor_timestamps?: boolean;
|
|
176
|
+
track_timestamps_staleness?: boolean;
|
|
177
|
+
fallback_scrape_protocol?: string;
|
|
178
|
+
/** Defaults to `/metrics`. */
|
|
179
|
+
metrics_path?: string;
|
|
180
|
+
/** `http` (default) or `https`. */
|
|
181
|
+
scheme?: "http" | "https";
|
|
182
|
+
/** URL query parameters sent with every scrape. */
|
|
183
|
+
params?: Record<string, string[]>;
|
|
184
|
+
enable_compression?: boolean;
|
|
185
|
+
static_configs?: StaticConfig[];
|
|
186
|
+
relabel_configs?: RelabelConfig[];
|
|
187
|
+
metric_relabel_configs?: RelabelConfig[];
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// ── Top-level sections ──────────────────────────────────────────────
|
|
191
|
+
|
|
192
|
+
/** `global:`. */
|
|
193
|
+
export interface PrometheusGlobalConfig extends ScrapeDefaults {
|
|
194
|
+
evaluation_interval?: string;
|
|
195
|
+
rule_query_offset?: string;
|
|
196
|
+
/** Labels added to every series sent to remote storage and every alert. */
|
|
197
|
+
external_labels?: LabelSet;
|
|
198
|
+
query_log_file?: string;
|
|
199
|
+
scrape_failure_log_file?: string;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/** One `alerting.alertmanagers` entry. */
|
|
203
|
+
export interface AlertmanagerTarget extends HttpClientConfig, ServiceDiscoveryConfigs {
|
|
204
|
+
timeout?: string;
|
|
205
|
+
api_version?: "v2";
|
|
206
|
+
path_prefix?: string;
|
|
207
|
+
scheme?: "http" | "https";
|
|
208
|
+
relabel_configs?: RelabelConfig[];
|
|
209
|
+
alert_relabel_configs?: RelabelConfig[];
|
|
210
|
+
static_configs?: StaticConfig[];
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/** `alerting:`. */
|
|
214
|
+
export interface PrometheusAlertingConfig {
|
|
215
|
+
alert_relabel_configs?: RelabelConfig[];
|
|
216
|
+
alertmanagers?: AlertmanagerTarget[];
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/** `remote_write[].queue_config`. */
|
|
220
|
+
export interface QueueConfig {
|
|
221
|
+
capacity?: number;
|
|
222
|
+
max_shards?: number;
|
|
223
|
+
min_shards?: number;
|
|
224
|
+
max_samples_per_send?: number;
|
|
225
|
+
batch_send_deadline?: string;
|
|
226
|
+
min_backoff?: string;
|
|
227
|
+
max_backoff?: string;
|
|
228
|
+
retry_on_http_429?: boolean;
|
|
229
|
+
sample_age_limit?: string;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/** One `remote_write` entry. */
|
|
233
|
+
export interface RemoteWriteConfig extends HttpClientConfig {
|
|
234
|
+
url: string;
|
|
235
|
+
name?: string;
|
|
236
|
+
remote_timeout?: string;
|
|
237
|
+
headers?: Record<string, string>;
|
|
238
|
+
write_relabel_configs?: RelabelConfig[];
|
|
239
|
+
send_exemplars?: boolean;
|
|
240
|
+
send_native_histograms?: boolean;
|
|
241
|
+
queue_config?: QueueConfig;
|
|
242
|
+
metadata_config?: { send?: boolean; send_interval?: string; max_samples_per_send?: number };
|
|
243
|
+
sigv4?: {
|
|
244
|
+
region?: string;
|
|
245
|
+
access_key?: string;
|
|
246
|
+
secret_key?: string;
|
|
247
|
+
profile?: string;
|
|
248
|
+
role_arn?: string;
|
|
249
|
+
};
|
|
250
|
+
azuread?: Record<string, unknown>;
|
|
251
|
+
google_iam?: Record<string, unknown>;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/** One `remote_read` entry. */
|
|
255
|
+
export interface RemoteReadConfig extends HttpClientConfig {
|
|
256
|
+
url: string;
|
|
257
|
+
name?: string;
|
|
258
|
+
remote_timeout?: string;
|
|
259
|
+
headers?: Record<string, string>;
|
|
260
|
+
read_recent?: boolean;
|
|
261
|
+
required_matchers?: LabelSet;
|
|
262
|
+
filter_external_labels?: boolean;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/** `otlp:`: how OTLP metrics pushed to Prometheus become series. */
|
|
266
|
+
export interface OtlpConfig {
|
|
267
|
+
promote_all_resource_attributes?: boolean;
|
|
268
|
+
promote_resource_attributes?: string[];
|
|
269
|
+
ignore_resource_attributes?: string[];
|
|
270
|
+
translation_strategy?: "UnderscoreEscapingWithSuffixes" | "UnderscoreEscapingWithoutSuffixes" | "NoUTF8EscapingWithSuffixes" | "NoTranslation";
|
|
271
|
+
keep_identifying_resource_attributes?: boolean;
|
|
272
|
+
convert_histograms_to_nhcb?: boolean;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/** The sections of a `prometheus.yml` besides its scrape jobs. */
|
|
276
|
+
export interface PrometheusConfigSections {
|
|
277
|
+
global?: PrometheusGlobalConfig;
|
|
278
|
+
alerting?: PrometheusAlertingConfig;
|
|
279
|
+
/** Paths or globs of rule files, relative to the config file. */
|
|
280
|
+
rule_files?: string[];
|
|
281
|
+
/** Paths or globs of files holding more `scrape_configs`. */
|
|
282
|
+
scrape_config_files?: string[];
|
|
283
|
+
remote_write?: RemoteWriteConfig[];
|
|
284
|
+
remote_read?: RemoteReadConfig[];
|
|
285
|
+
otlp?: OtlpConfig;
|
|
286
|
+
/** TSDB and exemplar settings, carried as written. */
|
|
287
|
+
storage?: Record<string, unknown>;
|
|
288
|
+
/** Where Prometheus sends its own traces, carried as written. */
|
|
289
|
+
tracing?: Record<string, unknown>;
|
|
290
|
+
/** Go runtime settings (`gogc`), carried as written. */
|
|
291
|
+
runtime?: Record<string, unknown>;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/** A whole `prometheus.yml`. */
|
|
295
|
+
export interface PrometheusConfigFile extends PrometheusConfigSections {
|
|
296
|
+
scrape_configs?: ScrapeJobConfig[];
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/** Top-level keys of `prometheus.yml`, in the order Prometheus documents them. */
|
|
300
|
+
export const PROMETHEUS_CONFIG_SECTIONS = Object.freeze([
|
|
301
|
+
"global",
|
|
302
|
+
"alerting",
|
|
303
|
+
"rule_files",
|
|
304
|
+
"scrape_config_files",
|
|
305
|
+
"scrape_configs",
|
|
306
|
+
"remote_write",
|
|
307
|
+
"remote_read",
|
|
308
|
+
"otlp",
|
|
309
|
+
"storage",
|
|
310
|
+
"tracing",
|
|
311
|
+
"runtime",
|
|
312
|
+
]);
|
|
313
|
+
|
|
314
|
+
const GLOBAL_ONLY_KEYS = ["scrape_interval", "scrape_timeout", "evaluation_interval", "external_labels", "rule_query_offset", "query_log_file"];
|
|
315
|
+
|
|
316
|
+
/** True when a parsed document has the shape of a `prometheus.yml`. */
|
|
317
|
+
export function looksLikePrometheusConfig(value: unknown): value is PrometheusConfigFile {
|
|
318
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
|
|
319
|
+
const v = value as Record<string, unknown>;
|
|
320
|
+
if ("apiVersion" in v || "kind" in v || "groups" in v || "route" in v || "receivers" in v) return false;
|
|
321
|
+
if (PROMETHEUS_CONFIG_SECTIONS.some((k) => k !== "global" && k !== "runtime" && k !== "storage" && k !== "tracing" && k in v)) {
|
|
322
|
+
return true;
|
|
323
|
+
}
|
|
324
|
+
// `global:` alone is Alertmanager's too; only Prometheus's own keys settle it.
|
|
325
|
+
const g = v.global;
|
|
326
|
+
return typeof g === "object" && g !== null && GLOBAL_ONLY_KEYS.some((k) => k in g);
|
|
327
|
+
}
|
package/src/detect.ts
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Template detection for the prometheus lexicon: a parsed document is ours
|
|
3
|
-
* when it is shaped like a rule file (`groups:` of named rule lists)
|
|
4
|
-
* `alertmanager.yml` (`route:` or `receivers:` at the top level)
|
|
3
|
+
* when it is shaped like a rule file (`groups:` of named rule lists), an
|
|
4
|
+
* `alertmanager.yml` (`route:` or `receivers:` at the top level) or a
|
|
5
|
+
* `prometheus.yml` (`scrape_configs:`, `remote_write:` and the like). Kept free
|
|
5
6
|
* of the plugin and the TypeScript compiler so it bundles for edge runtimes,
|
|
6
7
|
* like the other lexicons' `detect` modules.
|
|
7
8
|
*/
|
|
8
|
-
import { looksLikeAlertmanagerConfig, looksLikeRuleFile } from "./model";
|
|
9
|
+
import { looksLikeAlertmanagerConfig, looksLikePrometheusConfig, looksLikeRuleFile } from "./model";
|
|
9
10
|
|
|
10
11
|
export function detectTemplate(data: unknown): boolean {
|
|
11
|
-
return looksLikeRuleFile(data) || looksLikeAlertmanagerConfig(data);
|
|
12
|
+
return looksLikeRuleFile(data) || looksLikeAlertmanagerConfig(data) || looksLikePrometheusConfig(data);
|
|
12
13
|
}
|
|
@@ -9,6 +9,11 @@
|
|
|
9
9
|
"kind": "resource",
|
|
10
10
|
"lexicon": "prometheus"
|
|
11
11
|
},
|
|
12
|
+
"PrometheusConfig": {
|
|
13
|
+
"resourceType": "Prometheus::Config::PrometheusConfig",
|
|
14
|
+
"kind": "resource",
|
|
15
|
+
"lexicon": "prometheus"
|
|
16
|
+
},
|
|
12
17
|
"Receiver": {
|
|
13
18
|
"resourceType": "Prometheus::Alertmanager::Receiver",
|
|
14
19
|
"kind": "resource",
|
|
@@ -24,6 +29,11 @@
|
|
|
24
29
|
"kind": "resource",
|
|
25
30
|
"lexicon": "prometheus"
|
|
26
31
|
},
|
|
32
|
+
"ScrapeConfig": {
|
|
33
|
+
"resourceType": "Prometheus::Config::ScrapeConfig",
|
|
34
|
+
"kind": "resource",
|
|
35
|
+
"lexicon": "prometheus"
|
|
36
|
+
},
|
|
27
37
|
"TimeInterval": {
|
|
28
38
|
"resourceType": "Prometheus::Alertmanager::TimeInterval",
|
|
29
39
|
"kind": "resource",
|
package/src/import/generator.ts
CHANGED
|
@@ -24,6 +24,8 @@ import type { GeneratedFile, TypeScriptGenerator } from "@intentius/chant/import
|
|
|
24
24
|
import type { TemplateIR } from "@intentius/chant/import/parser";
|
|
25
25
|
import type {
|
|
26
26
|
AlertmanagerConfig,
|
|
27
|
+
PrometheusConfigFile,
|
|
28
|
+
ScrapeJobConfig,
|
|
27
29
|
InhibitRuleConfig,
|
|
28
30
|
ReceiverConfig,
|
|
29
31
|
RouteConfig,
|
|
@@ -35,8 +37,10 @@ import { ALERTMANAGER_GLOBAL_FIELDS, RECEIVER_INTEGRATION_TYPES } from "../model
|
|
|
35
37
|
import { PROMETHEUS_PIN } from "../pin";
|
|
36
38
|
import {
|
|
37
39
|
ALERTMANAGER_RESOURCE_TYPE,
|
|
40
|
+
PROMETHEUS_RESOURCE_TYPE,
|
|
38
41
|
RULE_FILE_RESOURCE_TYPE,
|
|
39
42
|
type AlertmanagerResourceProperties,
|
|
43
|
+
type PrometheusResourceProperties,
|
|
40
44
|
type RuleFileResourceProperties,
|
|
41
45
|
} from "./parser";
|
|
42
46
|
import { recognizeSlo } from "./slo";
|
|
@@ -259,6 +263,22 @@ const LEXICON_NAMES = [
|
|
|
259
263
|
"TimeInterval",
|
|
260
264
|
"TimePeriodConfig",
|
|
261
265
|
"InhibitRule",
|
|
266
|
+
"ScrapeConfig",
|
|
267
|
+
"ScrapeConfigProps",
|
|
268
|
+
"PrometheusConfig",
|
|
269
|
+
"StaticConfig",
|
|
270
|
+
"RelabelConfig",
|
|
271
|
+
"PrometheusGlobalConfig",
|
|
272
|
+
"PrometheusAlertingConfig",
|
|
273
|
+
"RemoteWriteConfig",
|
|
274
|
+
"RemoteReadConfig",
|
|
275
|
+
"OtlpConfig",
|
|
276
|
+
"KubernetesSDConfig",
|
|
277
|
+
"FileSDConfig",
|
|
278
|
+
"HttpSDConfig",
|
|
279
|
+
"DnsSDConfig",
|
|
280
|
+
"Ec2SDConfig",
|
|
281
|
+
"ConsulSDConfig",
|
|
262
282
|
"AlertmanagerSettings",
|
|
263
283
|
"AlertmanagerGlobalConfig",
|
|
264
284
|
"AlertmanagerTracingConfig",
|
|
@@ -579,6 +599,104 @@ export function generateAlertmanager(config: AlertmanagerConfig): { files: Gener
|
|
|
579
599
|
};
|
|
580
600
|
}
|
|
581
601
|
|
|
602
|
+
// ── prometheus.yml ───────────────────────────────────────────────────
|
|
603
|
+
|
|
604
|
+
/** The type each hoisted scrape config field is declared as; others use `ScrapeConfigProps["field"]`. */
|
|
605
|
+
const SCRAPE_FIELD_TYPES: Record<string, string> = {
|
|
606
|
+
static_configs: "StaticConfig[]",
|
|
607
|
+
relabel_configs: "RelabelConfig[]",
|
|
608
|
+
metric_relabel_configs: "RelabelConfig[]",
|
|
609
|
+
params: "Record<string, string[]>",
|
|
610
|
+
kubernetes_sd_configs: "KubernetesSDConfig[]",
|
|
611
|
+
file_sd_configs: "FileSDConfig[]",
|
|
612
|
+
http_sd_configs: "HttpSDConfig[]",
|
|
613
|
+
dns_sd_configs: "DnsSDConfig[]",
|
|
614
|
+
ec2_sd_configs: "Ec2SDConfig[]",
|
|
615
|
+
consul_sd_configs: "ConsulSDConfig[]",
|
|
616
|
+
};
|
|
617
|
+
|
|
618
|
+
/** The type each top-level section is declared as. */
|
|
619
|
+
const SECTION_TYPES: Record<string, string> = {
|
|
620
|
+
global: "PrometheusGlobalConfig",
|
|
621
|
+
alerting: "PrometheusAlertingConfig",
|
|
622
|
+
remote_write: "RemoteWriteConfig[]",
|
|
623
|
+
remote_read: "RemoteReadConfig[]",
|
|
624
|
+
otlp: "OtlpConfig",
|
|
625
|
+
storage: "Record<string, unknown>",
|
|
626
|
+
tracing: "Record<string, unknown>",
|
|
627
|
+
runtime: "Record<string, unknown>",
|
|
628
|
+
};
|
|
629
|
+
|
|
630
|
+
/** Generate the TypeScript declaring one `prometheus.yml`. */
|
|
631
|
+
export function generatePrometheusConfigFiles(config: PrometheusConfigFile): GeneratedFile[] {
|
|
632
|
+
const names = new Names([...LEXICON_NAMES, ...Object.keys(SECTION_TYPES)]);
|
|
633
|
+
const modules: Module[] = [];
|
|
634
|
+
|
|
635
|
+
for (const [mod, items] of chunk(config.scrape_configs ?? [], "scrape-configs", "Scrape jobs: where Prometheus pulls metrics from")) {
|
|
636
|
+
modules.push(mod);
|
|
637
|
+
mod.use("ScrapeConfig");
|
|
638
|
+
for (const job of items as ScrapeJobConfig[]) {
|
|
639
|
+
const v = names.claim(job.job_name, "Scrape", "scrape");
|
|
640
|
+
const lines: string[] = [];
|
|
641
|
+
const entries: Array<[string, string]> = [["job_name", stringLiteral(job.job_name)]];
|
|
642
|
+
for (const [key, value] of Object.entries(job)) {
|
|
643
|
+
if (key === "job_name" || value === undefined) continue;
|
|
644
|
+
if (!needsHoist(value)) {
|
|
645
|
+
entries.push([key, tsLiteral(value, 2, 4 + key.length)]);
|
|
646
|
+
continue;
|
|
647
|
+
}
|
|
648
|
+
const known = SCRAPE_FIELD_TYPES[key];
|
|
649
|
+
const type = known ?? (key.endsWith("_sd_configs") ? undefined : `ScrapeConfigProps["${key}"]`);
|
|
650
|
+
const c = names.claim(`${v} ${key.replace(/_configs$/, "")}`, "Config", "scrape");
|
|
651
|
+
if (type) {
|
|
652
|
+
if (type.startsWith("ScrapeConfigProps")) mod.types.add("ScrapeConfigProps");
|
|
653
|
+
else if (!type.startsWith("Record<")) mod.types.add(type.replace(/\[\]$/, ""));
|
|
654
|
+
const head = `const ${c}: ${type} = `;
|
|
655
|
+
lines.push(`${head}${tsLiteral(value, 0, head.length)};`);
|
|
656
|
+
} else {
|
|
657
|
+
lines.push(`// ${key} is not a discovery kind the lexicon types, so it is carried as data, untyped.`);
|
|
658
|
+
const head = `const ${c} = `;
|
|
659
|
+
lines.push(`${head}${tsLiteral(value, 0, head.length)};`);
|
|
660
|
+
}
|
|
661
|
+
entries.push([key, c]);
|
|
662
|
+
}
|
|
663
|
+
const ctor = `const ${v} = new ScrapeConfig(`;
|
|
664
|
+
lines.push(`${ctor}${objectOf(entries, 0, ctor.length)});`);
|
|
665
|
+
mod.block(lines);
|
|
666
|
+
mod.exports.push(v);
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
const sectionKeys = Object.keys(config).filter((k) => k !== "scrape_configs");
|
|
671
|
+
if (sectionKeys.length > 0) {
|
|
672
|
+
const mod = new Module("prometheus.ts", "The prometheus.yml sections besides the scrape jobs");
|
|
673
|
+
modules.push(mod);
|
|
674
|
+
mod.use("PrometheusConfig");
|
|
675
|
+
const lines: string[] = [];
|
|
676
|
+
const entries: Array<[string, string]> = [];
|
|
677
|
+
for (const key of sectionKeys) {
|
|
678
|
+
const value = (config as Record<string, unknown>)[key];
|
|
679
|
+
const type = SECTION_TYPES[key];
|
|
680
|
+
if (!type || !needsHoist(value)) {
|
|
681
|
+
entries.push([key, tsLiteral(value, 2, 4 + key.length)]);
|
|
682
|
+
continue;
|
|
683
|
+
}
|
|
684
|
+
if (!type.startsWith("Record<")) mod.types.add(type.replace(/\[\]$/, ""));
|
|
685
|
+
const c = names.claim(key.replace(/_/g, " "), "Config", "prometheus");
|
|
686
|
+
const head = `const ${c}: ${type} = `;
|
|
687
|
+
lines.push(`${head}${tsLiteral(value, 0, head.length)};`);
|
|
688
|
+
entries.push([key, c]);
|
|
689
|
+
}
|
|
690
|
+
const v = names.claim("prometheus", "Settings", "prometheus");
|
|
691
|
+
const ctor = `const ${v} = new PrometheusConfig(`;
|
|
692
|
+
lines.push(`${ctor}${objectOf(entries, 0, ctor.length)});`);
|
|
693
|
+
mod.block(lines);
|
|
694
|
+
mod.exports.push(v);
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
return modules.map((m) => ({ path: m.path, content: m.render() }));
|
|
698
|
+
}
|
|
699
|
+
|
|
582
700
|
/** The rule file and `alertmanager.yml` TypeScript generator `chant import` runs. */
|
|
583
701
|
export class PrometheusGenerator implements TypeScriptGenerator {
|
|
584
702
|
generate(ir: TemplateIR): GeneratedFile[] {
|
|
@@ -586,6 +704,8 @@ export class PrometheusGenerator implements TypeScriptGenerator {
|
|
|
586
704
|
for (const r of ir.resources) {
|
|
587
705
|
if (r.type === RULE_FILE_RESOURCE_TYPE) {
|
|
588
706
|
files.push(...generateRuleFileFiles((r.properties as unknown as RuleFileResourceProperties).file));
|
|
707
|
+
} else if (r.type === PROMETHEUS_RESOURCE_TYPE) {
|
|
708
|
+
files.push(...generatePrometheusConfigFiles((r.properties as unknown as PrometheusResourceProperties).config));
|
|
589
709
|
} else if (r.type === ALERTMANAGER_RESOURCE_TYPE) {
|
|
590
710
|
files.push(...generateAlertmanagerFiles((r.properties as unknown as AlertmanagerResourceProperties).config));
|
|
591
711
|
}
|
|
@@ -11,9 +11,9 @@ describe("parsePrometheusYaml", () => {
|
|
|
11
11
|
});
|
|
12
12
|
|
|
13
13
|
test("rejects a document that is neither, and one that is not a mapping", () => {
|
|
14
|
-
expect(() => parsePrometheusYaml("receivers:\n otlp: {}\n")).toThrow(/
|
|
14
|
+
expect(() => parsePrometheusYaml("receivers:\n otlp: {}\n")).toThrow(/is not a Prometheus rule file .*an alertmanager\.yml .*or a prometheus\.yml/);
|
|
15
15
|
expect(() => parsePrometheusYaml("- a\n- b\n")).toThrow(/YAML mapping/);
|
|
16
|
-
expect(() => parsePrometheusYaml("")).toThrow(/
|
|
16
|
+
expect(() => parsePrometheusYaml("")).toThrow(/is not a Prometheus rule file/);
|
|
17
17
|
});
|
|
18
18
|
|
|
19
19
|
test("a rule's values are strings, label values included, and a group's limit a number", () => {
|