@intentius/chant-lexicon-prometheus 0.97.0 → 0.99.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 +6 -1
- package/dist/alertmanager.d.ts +4 -2
- package/dist/alertmanager.d.ts.map +1 -1
- package/dist/build.d.ts.map +1 -1
- package/dist/codegen/docs.d.ts.map +1 -1
- package/dist/import/embedded.d.ts +14 -0
- package/dist/import/embedded.d.ts.map +1 -0
- package/dist/import/generator.d.ts +50 -0
- package/dist/import/generator.d.ts.map +1 -0
- package/dist/import/parser.d.ts +52 -0
- package/dist/import/parser.d.ts.map +1 -0
- package/dist/import/slo.d.ts +15 -0
- package/dist/import/slo.d.ts.map +1 -0
- package/dist/integrations.d.ts +531 -0
- package/dist/integrations.d.ts.map +1 -0
- package/dist/integrity.json +7 -7
- package/dist/lint/rules/literal-credential.d.ts.map +1 -1
- package/dist/lint/rules/prom-ast.d.ts +8 -0
- package/dist/lint/rules/prom-ast.d.ts.map +1 -1
- package/dist/lint/rules/promql-literal.d.ts.map +1 -1
- package/dist/manifest.json +1 -1
- package/dist/model.d.ts +77 -121
- package/dist/model.d.ts.map +1 -1
- package/dist/plugin.d.ts.map +1 -1
- package/dist/rules/literal-credential.ts +52 -15
- package/dist/rules/prom-ast.ts +36 -0
- package/dist/rules/promql-literal.ts +15 -1
- package/dist/skills/chant-prometheus-alertmanager.md +4 -0
- package/dist/skills/chant-prometheus.md +11 -0
- package/package.json +2 -2
- package/src/alertmanager.ts +4 -1
- package/src/build.ts +2 -0
- package/src/codegen/docs.ts +5 -1
- package/src/import/cli.test.ts +98 -0
- package/src/import/embedded.test.ts +56 -0
- package/src/import/embedded.ts +44 -0
- package/src/import/generated-types.e2e.test.ts +77 -0
- package/src/import/generator.test.ts +138 -0
- package/src/import/generator.ts +578 -0
- package/src/import/parser.test.ts +133 -0
- package/src/import/parser.ts +365 -0
- package/src/import/roundtrip.test.ts +358 -0
- package/src/import/slo.ts +175 -0
- package/src/import/testdata/alertmanager-full.yml +89 -0
- package/src/import/testdata/alertmanager-integrations.yml +215 -0
- package/src/import/testdata/fixtures.ts +122 -0
- package/src/import/testdata/rules-full.yml +42 -0
- package/src/import/testdata/upstream/alertmanager-conf-good.yml +120 -0
- package/src/import/testdata/upstream/alertmanager-mattermost-default-webhook-url-file.yml +16 -0
- package/src/import/testdata/upstream/alertmanager-opsgenie-default-apikey-file.yml +27 -0
- package/src/import/testdata/upstream/alertmanager-rocketchat-default-token-file.yml +25 -0
- package/src/import/testdata/upstream/alertmanager-route-labels.yml +65 -0
- package/src/import/testdata/upstream/alertmanager-simple.yml +130 -0
- package/src/import/testdata/upstream/alertmanager-sns-topic-arn.yml +17 -0
- package/src/import/testdata/upstream/alertmanager-telegram-default-bot-token-file.yml +16 -0
- package/src/import/testdata/upstream/alertmanager-victorops-default-apikey-file.yml +23 -0
- package/src/import/testdata/upstream/alertmanager-wechat-default-api-secret-file.yml +18 -0
- package/src/import/testdata/upstream/prometheus-alerting-rules.yml +15 -0
- package/src/import/testdata/upstream/prometheus-alerting-templates.yml +23 -0
- package/src/import/testdata/upstream/prometheus-recording-rules.yml +7 -0
- package/src/integrations.ts +538 -0
- package/src/lint/rules/literal-credential.ts +52 -15
- package/src/lint/rules/prom-ast.ts +36 -0
- package/src/lint/rules/promql-literal.ts +15 -1
- package/src/lint/rules/rules.test.ts +58 -0
- package/src/model.ts +157 -109
- package/src/plugin.ts +15 -0
- package/src/skills/chant-prometheus-alertmanager.md +4 -0
- package/src/skills/chant-prometheus.md +11 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as ts from "typescript";
|
|
2
2
|
import type { LintRule, LintDiagnostic, LintContext } from "@intentius/chant/lint/rule";
|
|
3
|
-
import { calleeName, literalText, position, propertyName } from "./prom-ast";
|
|
3
|
+
import { calleeName, constInitializers, literalText, position, propertyName, resolveConst } from "./prom-ast";
|
|
4
4
|
import { checkPromql } from "../../promql";
|
|
5
5
|
|
|
6
6
|
/**
|
|
@@ -21,7 +21,21 @@ export const promqlLiteralRule: LintRule = {
|
|
|
21
21
|
const diagnostics: LintDiagnostic[] = [];
|
|
22
22
|
const source = context.sourceFile;
|
|
23
23
|
|
|
24
|
+
const consts = constInitializers(source);
|
|
25
|
+
const scanned = new Set<ts.Node>();
|
|
26
|
+
|
|
27
|
+
// Rules lifted into a const (`rules: apiRules`) are followed to it and
|
|
28
|
+
// checked where the group uses them.
|
|
29
|
+
const follow = (value: ts.Expression) => {
|
|
30
|
+
const init = resolveConst(value, consts);
|
|
31
|
+
if (init !== value && !scanned.has(init)) {
|
|
32
|
+
scanned.add(init);
|
|
33
|
+
scan(init);
|
|
34
|
+
}
|
|
35
|
+
};
|
|
24
36
|
const scan = (node: ts.Node) => {
|
|
37
|
+
if (ts.isShorthandPropertyAssignment(node)) follow(node.name);
|
|
38
|
+
if (ts.isPropertyAssignment(node) && ts.isIdentifier(node.initializer)) follow(node.initializer);
|
|
25
39
|
if (ts.isPropertyAssignment(node) && propertyName(node) === "expr") {
|
|
26
40
|
const text = literalText(node.initializer);
|
|
27
41
|
if (text !== undefined) {
|
|
@@ -44,6 +44,52 @@ describe("PROM001 literal credential", () => {
|
|
|
44
44
|
);
|
|
45
45
|
expect(diags).toEqual([]);
|
|
46
46
|
});
|
|
47
|
+
|
|
48
|
+
test("follows values lifted into named consts, shorthand props and spreads", () => {
|
|
49
|
+
const diags = literalCredentialRule.check(
|
|
50
|
+
ctx(`
|
|
51
|
+
const heartbeatEmail: EmailConfig[] = [{ to: "a@example.com", auth_password: "hunter2" }];
|
|
52
|
+
const heartbeat = new Receiver({ name: "heartbeat", email_configs: heartbeatEmail });
|
|
53
|
+
const oncallUntyped = { opsgenie_configs: [{ api_key: "x" }], pagerduty_configs: [{ service_key: "k" }] };
|
|
54
|
+
const oncall = new Receiver({ name: "oncall", ...oncallUntyped });
|
|
55
|
+
const global: AlertmanagerGlobalConfig = { smtp_auth_password: "p" };
|
|
56
|
+
const settings = new AlertmanagerSettings({ global });
|
|
57
|
+
const safe: PagerDutyConfig[] = [{ routing_key_file: "/etc/pd" }];
|
|
58
|
+
const pager = new Receiver({ name: "pager", pagerduty_configs: safe });
|
|
59
|
+
`),
|
|
60
|
+
);
|
|
61
|
+
expect(diags.map((d) => d.message.split("`")[1])).toEqual(["auth_password", "api_key", "service_key", "smtp_auth_password"]);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
test("covers every integration's credential, and api_url and routing_key only where they are one", () => {
|
|
65
|
+
const diags = literalCredentialRule.check(
|
|
66
|
+
ctx(`
|
|
67
|
+
const chatSlack: SlackConfig[] = [{ api_url: "https://hooks.slack.com/services/T/B/X", app_token: "xoxb-1" }];
|
|
68
|
+
new Receiver({
|
|
69
|
+
name: "chat",
|
|
70
|
+
slack_configs: chatSlack,
|
|
71
|
+
discord_configs: [{ webhook_url: "https://discord.com/api/webhooks/1/x" }],
|
|
72
|
+
telegram_configs: [{ bot_token: "123:abc", chat_id: 1 }],
|
|
73
|
+
webex_configs: [{ api_url: "https://webexapis.com/v1/messages", room_id: "r" }],
|
|
74
|
+
victorops_configs: [{ api_key: "k", routing_key: "ops" }],
|
|
75
|
+
pushover_configs: [{ user_key: "u", token: "t" }],
|
|
76
|
+
rocketchat_configs: [{ token_id: "i", token_file: "/etc/rc" }],
|
|
77
|
+
});
|
|
78
|
+
new AlertmanagerSettings({ global: { telegram_bot_token: "123:abc", webex_api_url: "https://webexapis.com/v1/messages" } });
|
|
79
|
+
`),
|
|
80
|
+
);
|
|
81
|
+
expect(diags.map((d) => d.message.split("`")[1])).toEqual([
|
|
82
|
+
"api_url",
|
|
83
|
+
"app_token",
|
|
84
|
+
"webhook_url",
|
|
85
|
+
"bot_token",
|
|
86
|
+
"api_key",
|
|
87
|
+
"user_key",
|
|
88
|
+
"token",
|
|
89
|
+
"token_id",
|
|
90
|
+
"telegram_bot_token",
|
|
91
|
+
]);
|
|
92
|
+
});
|
|
47
93
|
});
|
|
48
94
|
|
|
49
95
|
describe("PROM002 PromQL literal", () => {
|
|
@@ -61,6 +107,18 @@ describe("PROM002 PromQL literal", () => {
|
|
|
61
107
|
expect(diags[0].line).toBe(3);
|
|
62
108
|
});
|
|
63
109
|
|
|
110
|
+
test("follows rules lifted into a named const", () => {
|
|
111
|
+
const diags = promqlLiteralRule.check(
|
|
112
|
+
ctx(`
|
|
113
|
+
const apiRules: Rule[] = [{ record: "a:b", expr: "sum(" }, { alert: "A", expr: "up == 0" }];
|
|
114
|
+
const api = new RuleGroup({ name: "api", rules: apiRules });
|
|
115
|
+
const rules: Rule[] = [{ record: "c:d", expr: "rate(x[5m]" }];
|
|
116
|
+
const other = new RuleGroup({ name: "other", rules });
|
|
117
|
+
`),
|
|
118
|
+
);
|
|
119
|
+
expect(diags.map((d) => d.line)).toEqual([2, 4]);
|
|
120
|
+
});
|
|
121
|
+
|
|
64
122
|
test("ignores built expressions and exprs outside a RuleGroup", () => {
|
|
65
123
|
const diags = promqlLiteralRule.check(
|
|
66
124
|
ctx(`
|
package/src/model.ts
CHANGED
|
@@ -13,6 +13,31 @@
|
|
|
13
13
|
* docs apply word for word.
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
|
+
import {
|
|
17
|
+
RECEIVER_INTEGRATION_TYPES,
|
|
18
|
+
type DiscordConfig,
|
|
19
|
+
type EmailConfig,
|
|
20
|
+
type HttpClientConfig,
|
|
21
|
+
type IncidentioConfig,
|
|
22
|
+
type JiraConfig,
|
|
23
|
+
type MattermostConfig,
|
|
24
|
+
type MSTeamsConfig,
|
|
25
|
+
type MSTeamsV2Config,
|
|
26
|
+
type OpsGenieConfig,
|
|
27
|
+
type PagerDutyConfig,
|
|
28
|
+
type PushoverConfig,
|
|
29
|
+
type ReceiverIntegration,
|
|
30
|
+
type RocketchatConfig,
|
|
31
|
+
type SlackConfig,
|
|
32
|
+
type SNSConfig,
|
|
33
|
+
type TelegramConfig,
|
|
34
|
+
type TlsConfig,
|
|
35
|
+
type VictorOpsConfig,
|
|
36
|
+
type WebexConfig,
|
|
37
|
+
type WebhookConfig,
|
|
38
|
+
type WechatConfig,
|
|
39
|
+
} from "./integrations";
|
|
40
|
+
|
|
16
41
|
/** Labels or annotations: string keys to string values. */
|
|
17
42
|
export type LabelSet = Record<string, string>;
|
|
18
43
|
|
|
@@ -78,112 +103,31 @@ export function ruleName(rule: RuleConfig): string {
|
|
|
78
103
|
|
|
79
104
|
// ── Alertmanager ────────────────────────────────────────────────────
|
|
80
105
|
|
|
81
|
-
|
|
82
|
-
export interface HttpClientConfig {
|
|
83
|
-
basic_auth?: { username?: string; username_file?: string; password?: string; password_file?: string };
|
|
84
|
-
authorization?: { type?: string; credentials?: string; credentials_file?: string };
|
|
85
|
-
bearer_token?: string;
|
|
86
|
-
bearer_token_file?: string;
|
|
87
|
-
oauth2?: Record<string, unknown>;
|
|
88
|
-
proxy_url?: string;
|
|
89
|
-
follow_redirects?: boolean;
|
|
90
|
-
enable_http2?: boolean;
|
|
91
|
-
tls_config?: {
|
|
92
|
-
ca_file?: string;
|
|
93
|
-
cert_file?: string;
|
|
94
|
-
key_file?: string;
|
|
95
|
-
server_name?: string;
|
|
96
|
-
insecure_skip_verify?: boolean;
|
|
97
|
-
min_version?: string;
|
|
98
|
-
};
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
interface NotifierBase {
|
|
102
|
-
/** Whether to notify about resolved alerts too. */
|
|
103
|
-
send_resolved?: boolean;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
export interface WebhookConfig extends NotifierBase {
|
|
107
|
-
/** Where alerts are POSTed. Set this or `url_file`. */
|
|
108
|
-
url?: string;
|
|
109
|
-
url_file?: string;
|
|
110
|
-
http_config?: HttpClientConfig;
|
|
111
|
-
/** Most alerts per message; 0 sends them all. */
|
|
112
|
-
max_alerts?: number;
|
|
113
|
-
timeout?: string;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
export interface EmailConfig extends NotifierBase {
|
|
117
|
-
to: string;
|
|
118
|
-
from?: string;
|
|
119
|
-
/** `host:port` of the SMTP server. Falls back to `global.smtp_smarthost`. */
|
|
120
|
-
smarthost?: string;
|
|
121
|
-
hello?: string;
|
|
122
|
-
auth_username?: string;
|
|
123
|
-
auth_password?: string;
|
|
124
|
-
auth_password_file?: string;
|
|
125
|
-
auth_secret?: string;
|
|
126
|
-
auth_secret_file?: string;
|
|
127
|
-
auth_identity?: string;
|
|
128
|
-
require_tls?: boolean;
|
|
129
|
-
tls_config?: HttpClientConfig["tls_config"];
|
|
130
|
-
html?: string;
|
|
131
|
-
text?: string;
|
|
132
|
-
headers?: Record<string, string>;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
export interface SlackConfig extends NotifierBase {
|
|
136
|
-
/** The incoming-webhook URL. It is a credential: prefer `api_url_file`. */
|
|
137
|
-
api_url?: string;
|
|
138
|
-
api_url_file?: string;
|
|
139
|
-
channel?: string;
|
|
140
|
-
username?: string;
|
|
141
|
-
color?: string;
|
|
142
|
-
title?: string;
|
|
143
|
-
title_link?: string;
|
|
144
|
-
pretext?: string;
|
|
145
|
-
text?: string;
|
|
146
|
-
footer?: string;
|
|
147
|
-
fallback?: string;
|
|
148
|
-
icon_emoji?: string;
|
|
149
|
-
icon_url?: string;
|
|
150
|
-
link_names?: boolean;
|
|
151
|
-
short_fields?: boolean;
|
|
152
|
-
mrkdwn_in?: string[];
|
|
153
|
-
fields?: Array<{ title: string; value: string; short?: boolean }>;
|
|
154
|
-
actions?: Array<Record<string, unknown>>;
|
|
155
|
-
http_config?: HttpClientConfig;
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
export interface PagerDutyConfig extends NotifierBase {
|
|
159
|
-
/** Events API v2 integration key. A credential: prefer `routing_key_file`. */
|
|
160
|
-
routing_key?: string;
|
|
161
|
-
routing_key_file?: string;
|
|
162
|
-
/** Events API v1 integration key. A credential: prefer `service_key_file`. */
|
|
163
|
-
service_key?: string;
|
|
164
|
-
service_key_file?: string;
|
|
165
|
-
url?: string;
|
|
166
|
-
client?: string;
|
|
167
|
-
client_url?: string;
|
|
168
|
-
description?: string;
|
|
169
|
-
severity?: string;
|
|
170
|
-
class?: string;
|
|
171
|
-
component?: string;
|
|
172
|
-
group?: string;
|
|
173
|
-
source?: string;
|
|
174
|
-
details?: Record<string, string>;
|
|
175
|
-
links?: Array<{ href: string; text?: string }>;
|
|
176
|
-
images?: Array<{ src: string; alt?: string; href?: string }>;
|
|
177
|
-
http_config?: HttpClientConfig;
|
|
178
|
-
}
|
|
106
|
+
export * from "./integrations";
|
|
179
107
|
|
|
180
108
|
/** One entry under `receivers:`. */
|
|
181
109
|
export interface ReceiverConfig {
|
|
182
110
|
name: string;
|
|
183
|
-
|
|
111
|
+
/** Labels on the receiver, exposed to notification templates. */
|
|
112
|
+
labels?: LabelSet;
|
|
113
|
+
discord_configs?: DiscordConfig[];
|
|
184
114
|
email_configs?: EmailConfig[];
|
|
185
|
-
|
|
115
|
+
incidentio_configs?: IncidentioConfig[];
|
|
186
116
|
pagerduty_configs?: PagerDutyConfig[];
|
|
117
|
+
slack_configs?: SlackConfig[];
|
|
118
|
+
webhook_configs?: WebhookConfig[];
|
|
119
|
+
opsgenie_configs?: OpsGenieConfig[];
|
|
120
|
+
wechat_configs?: WechatConfig[];
|
|
121
|
+
pushover_configs?: PushoverConfig[];
|
|
122
|
+
victorops_configs?: VictorOpsConfig[];
|
|
123
|
+
sns_configs?: SNSConfig[];
|
|
124
|
+
telegram_configs?: TelegramConfig[];
|
|
125
|
+
webex_configs?: WebexConfig[];
|
|
126
|
+
msteams_configs?: MSTeamsConfig[];
|
|
127
|
+
msteamsv2_configs?: MSTeamsV2Config[];
|
|
128
|
+
jira_configs?: JiraConfig[];
|
|
129
|
+
rocketchat_configs?: RocketchatConfig[];
|
|
130
|
+
mattermost_configs?: MattermostConfig[];
|
|
187
131
|
}
|
|
188
132
|
|
|
189
133
|
/** A route in the routing tree. The top-level `route:` is the root. */
|
|
@@ -198,11 +142,18 @@ export interface RouteConfig {
|
|
|
198
142
|
repeat_interval?: string;
|
|
199
143
|
mute_time_intervals?: string[];
|
|
200
144
|
active_time_intervals?: string[];
|
|
145
|
+
/**
|
|
146
|
+
* Labels on the route, inherited by child routes and exposed to
|
|
147
|
+
* notification templates as `routeLabels`. Values may be Go templates.
|
|
148
|
+
*/
|
|
149
|
+
labels?: LabelSet;
|
|
201
150
|
routes?: RouteConfig[];
|
|
202
151
|
}
|
|
203
152
|
|
|
204
153
|
/** One entry under `inhibit_rules:`. */
|
|
205
154
|
export interface InhibitRuleConfig {
|
|
155
|
+
/** A name for the rule, shown in Alertmanager's logs and metrics. */
|
|
156
|
+
name?: string;
|
|
206
157
|
source_matchers?: string[];
|
|
207
158
|
target_matchers?: string[];
|
|
208
159
|
/** Labels that must be equal on source and target for the inhibition to apply. */
|
|
@@ -235,12 +186,18 @@ export interface TimeIntervalConfig {
|
|
|
235
186
|
time_intervals: TimePeriodConfig[];
|
|
236
187
|
}
|
|
237
188
|
|
|
238
|
-
/**
|
|
189
|
+
/**
|
|
190
|
+
* `global:` settings: defaults the receivers fall back to. Credentials with
|
|
191
|
+
* a `*_file` sibling are best set through it; PROM001 flags a literal.
|
|
192
|
+
*/
|
|
239
193
|
export interface AlertmanagerGlobalConfig {
|
|
194
|
+
/** How long an alert that stops being updated stays firing. */
|
|
240
195
|
resolve_timeout?: string;
|
|
196
|
+
http_config?: HttpClientConfig;
|
|
197
|
+
jira_api_url?: string;
|
|
241
198
|
smtp_from?: string;
|
|
242
|
-
smtp_smarthost?: string;
|
|
243
199
|
smtp_hello?: string;
|
|
200
|
+
smtp_smarthost?: string;
|
|
244
201
|
smtp_auth_username?: string;
|
|
245
202
|
smtp_auth_password?: string;
|
|
246
203
|
smtp_auth_password_file?: string;
|
|
@@ -248,10 +205,93 @@ export interface AlertmanagerGlobalConfig {
|
|
|
248
205
|
smtp_auth_secret_file?: string;
|
|
249
206
|
smtp_auth_identity?: string;
|
|
250
207
|
smtp_require_tls?: boolean;
|
|
208
|
+
smtp_tls_config?: TlsConfig;
|
|
209
|
+
smtp_force_implicit_tls?: boolean;
|
|
251
210
|
slack_api_url?: string;
|
|
252
211
|
slack_api_url_file?: string;
|
|
212
|
+
slack_app_token?: string;
|
|
213
|
+
slack_app_token_file?: string;
|
|
214
|
+
slack_app_url?: string;
|
|
253
215
|
pagerduty_url?: string;
|
|
254
|
-
|
|
216
|
+
opsgenie_api_url?: string;
|
|
217
|
+
opsgenie_api_key?: string;
|
|
218
|
+
opsgenie_api_key_file?: string;
|
|
219
|
+
wechat_api_url?: string;
|
|
220
|
+
wechat_api_secret?: string;
|
|
221
|
+
wechat_api_secret_file?: string;
|
|
222
|
+
wechat_api_corp_id?: string;
|
|
223
|
+
victorops_api_url?: string;
|
|
224
|
+
victorops_api_key?: string;
|
|
225
|
+
victorops_api_key_file?: string;
|
|
226
|
+
telegram_api_url?: string;
|
|
227
|
+
telegram_bot_token?: string;
|
|
228
|
+
telegram_bot_token_file?: string;
|
|
229
|
+
webex_api_url?: string;
|
|
230
|
+
rocketchat_api_url?: string;
|
|
231
|
+
rocketchat_token?: string;
|
|
232
|
+
rocketchat_token_file?: string;
|
|
233
|
+
rocketchat_token_id?: string;
|
|
234
|
+
rocketchat_token_id_file?: string;
|
|
235
|
+
mattermost_webhook_url?: string;
|
|
236
|
+
mattermost_webhook_url_file?: string;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/** Every `global:` field, as a list, for code that reads a parsed file. */
|
|
240
|
+
export const ALERTMANAGER_GLOBAL_FIELDS = Object.freeze(Object.keys({
|
|
241
|
+
resolve_timeout: true,
|
|
242
|
+
http_config: true,
|
|
243
|
+
jira_api_url: true,
|
|
244
|
+
smtp_from: true,
|
|
245
|
+
smtp_hello: true,
|
|
246
|
+
smtp_smarthost: true,
|
|
247
|
+
smtp_auth_username: true,
|
|
248
|
+
smtp_auth_password: true,
|
|
249
|
+
smtp_auth_password_file: true,
|
|
250
|
+
smtp_auth_secret: true,
|
|
251
|
+
smtp_auth_secret_file: true,
|
|
252
|
+
smtp_auth_identity: true,
|
|
253
|
+
smtp_require_tls: true,
|
|
254
|
+
smtp_tls_config: true,
|
|
255
|
+
smtp_force_implicit_tls: true,
|
|
256
|
+
slack_api_url: true,
|
|
257
|
+
slack_api_url_file: true,
|
|
258
|
+
slack_app_token: true,
|
|
259
|
+
slack_app_token_file: true,
|
|
260
|
+
slack_app_url: true,
|
|
261
|
+
pagerduty_url: true,
|
|
262
|
+
opsgenie_api_url: true,
|
|
263
|
+
opsgenie_api_key: true,
|
|
264
|
+
opsgenie_api_key_file: true,
|
|
265
|
+
wechat_api_url: true,
|
|
266
|
+
wechat_api_secret: true,
|
|
267
|
+
wechat_api_secret_file: true,
|
|
268
|
+
wechat_api_corp_id: true,
|
|
269
|
+
victorops_api_url: true,
|
|
270
|
+
victorops_api_key: true,
|
|
271
|
+
victorops_api_key_file: true,
|
|
272
|
+
telegram_api_url: true,
|
|
273
|
+
telegram_bot_token: true,
|
|
274
|
+
telegram_bot_token_file: true,
|
|
275
|
+
webex_api_url: true,
|
|
276
|
+
rocketchat_api_url: true,
|
|
277
|
+
rocketchat_token: true,
|
|
278
|
+
rocketchat_token_file: true,
|
|
279
|
+
rocketchat_token_id: true,
|
|
280
|
+
rocketchat_token_id_file: true,
|
|
281
|
+
mattermost_webhook_url: true,
|
|
282
|
+
mattermost_webhook_url_file: true,
|
|
283
|
+
} satisfies Record<keyof AlertmanagerGlobalConfig, true>)) as readonly (keyof AlertmanagerGlobalConfig)[];
|
|
284
|
+
|
|
285
|
+
/** `tracing:` settings: where Alertmanager sends its own traces. */
|
|
286
|
+
export interface AlertmanagerTracingConfig {
|
|
287
|
+
client_type?: "grpc" | "http";
|
|
288
|
+
endpoint?: string;
|
|
289
|
+
sampling_fraction?: number;
|
|
290
|
+
insecure?: boolean;
|
|
291
|
+
headers?: Record<string, string>;
|
|
292
|
+
compression?: string;
|
|
293
|
+
timeout?: string;
|
|
294
|
+
tls_config?: TlsConfig;
|
|
255
295
|
}
|
|
256
296
|
|
|
257
297
|
/** A whole `alertmanager.yml`, the file `amtool check-config` reads. */
|
|
@@ -262,19 +302,27 @@ export interface AlertmanagerConfig {
|
|
|
262
302
|
inhibit_rules?: InhibitRuleConfig[];
|
|
263
303
|
receivers?: ReceiverConfig[];
|
|
264
304
|
time_intervals?: TimeIntervalConfig[];
|
|
305
|
+
tracing?: AlertmanagerTracingConfig;
|
|
265
306
|
}
|
|
266
307
|
|
|
267
|
-
/** The
|
|
268
|
-
export const RECEIVER_INTEGRATIONS =
|
|
308
|
+
/** The receiver integrations, every one Alertmanager defines. */
|
|
309
|
+
export const RECEIVER_INTEGRATIONS = Object.keys(RECEIVER_INTEGRATION_TYPES) as readonly ReceiverIntegration[];
|
|
269
310
|
|
|
270
311
|
/** True when a parsed document has the shape of a Prometheus rule file. */
|
|
271
312
|
export function looksLikeRuleFile(value: unknown): value is RuleFileConfig {
|
|
272
313
|
if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
|
|
273
|
-
const
|
|
314
|
+
const v = value as Record<string, unknown>;
|
|
315
|
+
const groups = v.groups;
|
|
274
316
|
if (!Array.isArray(groups)) return false;
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
);
|
|
317
|
+
// Grafana's alerting provisioning files also hold `groups:` of named rule lists. Prometheus reads rule
|
|
318
|
+
// files strictly (unknown keys are errors), so `apiVersion`, a group `folder` or a rule's `data` means Grafana.
|
|
319
|
+
if ("apiVersion" in v) return false;
|
|
320
|
+
return groups.every((g) => {
|
|
321
|
+
if (typeof g !== "object" || g === null) return false;
|
|
322
|
+
const group = g as Record<string, unknown>;
|
|
323
|
+
if (typeof group.name !== "string" || !Array.isArray(group.rules) || "folder" in group) return false;
|
|
324
|
+
return !group.rules.some((r) => typeof r === "object" && r !== null && "data" in r);
|
|
325
|
+
});
|
|
278
326
|
}
|
|
279
327
|
|
|
280
328
|
/** True when a parsed document has the shape of an `alertmanager.yml`. */
|
package/src/plugin.ts
CHANGED
|
@@ -9,6 +9,9 @@ import { prometheusAuditCatalog } from "./lint/audit-catalog";
|
|
|
9
9
|
import { completions } from "./lsp/completions";
|
|
10
10
|
import { hover } from "./lsp/hover";
|
|
11
11
|
import { detectTemplate } from "./detect";
|
|
12
|
+
import { PrometheusParser } from "./import/parser";
|
|
13
|
+
import { PrometheusGenerator } from "./import/generator";
|
|
14
|
+
import { ruleGroupsImporter } from "./import/embedded";
|
|
12
15
|
import { initTemplates } from "./init-templates";
|
|
13
16
|
import { prometheusSkills } from "./skill-defs";
|
|
14
17
|
import { CATALOG } from "./catalog";
|
|
@@ -110,6 +113,18 @@ export const prometheusPlugin: LexiconPlugin = {
|
|
|
110
113
|
return detectTemplate(data);
|
|
111
114
|
},
|
|
112
115
|
|
|
116
|
+
templateParser() {
|
|
117
|
+
return new PrometheusParser();
|
|
118
|
+
},
|
|
119
|
+
|
|
120
|
+
templateGenerator() {
|
|
121
|
+
return new PrometheusGenerator();
|
|
122
|
+
},
|
|
123
|
+
|
|
124
|
+
embeddedImporters() {
|
|
125
|
+
return [ruleGroupsImporter];
|
|
126
|
+
},
|
|
127
|
+
|
|
113
128
|
initTemplates(template?: string) {
|
|
114
129
|
return initTemplates(template);
|
|
115
130
|
},
|
|
@@ -47,6 +47,10 @@ export const pageMutesTicket = new InhibitRule({
|
|
|
47
47
|
});
|
|
48
48
|
```
|
|
49
49
|
|
|
50
|
+
## Starting from an existing alertmanager.yml
|
|
51
|
+
|
|
52
|
+
Import it rather than retyping it: `chant import alertmanager.yml --output src` (with `--lexicon prometheus` outside a chant project). The importer writes `receivers.ts`, `time-intervals.ts`, `routes.ts` (the root `Route`, children as `RouteProps` that reference receivers and time intervals by variable), `inhibit-rules.ts` and `settings.ts`. `*_file` paths and Go templates are kept as written; a literal credential is imported as found and PROM001 reports it, so offer to move it to the `*_file` field. Every integration comes back typed (`OpsGenieConfig[]`, `MSTeamsV2Config[]`, ...); a receiver or `global` key Alertmanager doesn't define is spread in from an untyped const, with a warning. `match`/`match_re` and the top-level `mute_time_intervals` come back in their current spelling (`matchers`, `time_intervals`), and `chant import` prints a warning for each.
|
|
53
|
+
|
|
50
54
|
## What the checks hold you to
|
|
51
55
|
|
|
52
56
|
- One root route (the `Route` no other route nests), with a receiver and no matchers (PROM205).
|
|
@@ -15,6 +15,17 @@ The prometheus lexicon (`@intentius/chant-lexicon-prometheus`) types Prometheus
|
|
|
15
15
|
export default { lexicons: ["prometheus"] };
|
|
16
16
|
```
|
|
17
17
|
|
|
18
|
+
## Starting from an existing rule file
|
|
19
|
+
|
|
20
|
+
When the user already has a rule file, import it rather than retyping it:
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
chant import rules.yml --output src # add --lexicon prometheus outside a chant project
|
|
24
|
+
chant build src --lexicon prometheus -o rules.yml # the same groups back
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
The importer writes `rules.ts` (per group, a `Rule[]` const of plain objects and a `RuleGroup`) and `slos.ts` for any group an `Slo()` built, as the `Slo` call. Multi-line expressions become template literals and Go templates in annotations stay as written. Read the warnings `chant import` prints: they name any field Prometheus doesn't define, which is not carried.
|
|
28
|
+
|
|
18
29
|
## Declaring a group
|
|
19
30
|
|
|
20
31
|
A group takes the rule file's own keys (`name`, `interval`, `query_offset`, `limit`, `labels`, `rules`). Rules are plain objects: `record` + `expr` for a recording rule, `alert` + `expr` (+ `for`, `keep_firing_for`, `labels`, `annotations`) for an alerting rule.
|