amlint 0.1.0__tar.gz

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.
amlint-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,109 @@
1
+ Metadata-Version: 2.4
2
+ Name: amlint
3
+ Version: 0.1.0
4
+ Summary: Semantic linter for Prometheus Alertmanager configs
5
+ Author: danikdanik2013
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/danikdanik2013/amlint
8
+ Project-URL: Repository, https://github.com/danikdanik2013/amlint
9
+ Project-URL: Bug Tracker, https://github.com/danikdanik2013/amlint/issues
10
+ Keywords: alertmanager,prometheus,linter,devops,monitoring
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Environment :: Console
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Intended Audience :: System Administrators
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.9
18
+ Classifier: Programming Language :: Python :: 3.10
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Topic :: System :: Monitoring
22
+ Classifier: Topic :: Utilities
23
+ Requires-Python: >=3.9
24
+ Description-Content-Type: text/markdown
25
+ Requires-Dist: pyyaml>=5.1
26
+ Provides-Extra: dev
27
+ Requires-Dist: pytest>=7; extra == "dev"
28
+
29
+ # amlint
30
+
31
+ Semantic linter for Prometheus Alertmanager configs.
32
+
33
+ `amtool check-config` validates syntax. **amlint validates semantics:**
34
+ will alerts actually reach a receiver, does the inhibition rule do anything,
35
+ are there unreachable routing branches? These are the bugs that burn teams —
36
+ config is valid, alerts silently vanish.
37
+
38
+ ## Why
39
+
40
+ Alertmanager configs are YAML routing trees with inhibition rules and receivers.
41
+ The most painful mistakes are syntactically valid:
42
+
43
+ - route references a receiver that doesn't exist → **alerts are dropped**
44
+ - inhibition without `equal` → silences unrelated alerts, you think it's quiet, there's actually a fire
45
+ - catch-all branch before specific ones → specific branches **are unreachable**
46
+ - `match_re` that doesn't compile
47
+ - `group_by` that doesn't behave the way you think
48
+
49
+ `amtool` won't catch any of this. amlint will.
50
+
51
+ ## Install
52
+
53
+ ```bash
54
+ pip install pyyaml
55
+ pip install -e .
56
+ ```
57
+
58
+ ## Usage
59
+
60
+ ```bash
61
+ amlint check alertmanager.yml
62
+ amlint check alertmanager.yml --strict # WARN also exits non-zero
63
+ amlint check alertmanager.yml --format json
64
+ ```
65
+
66
+ Example output:
67
+
68
+ ```
69
+ ERROR Route references receiver 'pager-team' which is not defined in receivers. Alerts matched here will be dropped.
70
+ ↳ route.routes[1] [undefined-receiver]
71
+
72
+ WARN Catch-all route (no matchers) with continue:false will intercept all alerts — 2 subsequent sibling(s) are unreachable.
73
+ ↳ route.routes[0] [unreachable-route]
74
+
75
+ 2 error · 3 warn · 1 info
76
+ ```
77
+
78
+ Exit code `1` on ERROR — ready for CI. `--strict` makes WARN block too.
79
+
80
+ ## CI example
81
+
82
+ ```yaml
83
+ # .github/workflows/lint.yml
84
+ - name: Lint Alertmanager config
85
+ run: amlint check alertmanager.yml --strict
86
+ ```
87
+
88
+ ## Checks
89
+
90
+ | code | level | what it catches |
91
+ |------|-------|-----------------|
92
+ | `undefined-receiver` | error | route references a receiver that doesn't exist |
93
+ | `bad-regex` | error | `match_re` pattern fails to compile |
94
+ | `no-root-route` | error | no root `route` defined |
95
+ | `inhibit-no-equal` | warn | inhibition without `equal` silences too broadly |
96
+ | `unreachable-route` | warn | catch-all hides subsequent sibling routes |
97
+ | `groupby-ellipsis` | warn | `...` mixed with explicit labels in `group_by` |
98
+ | `inhibit-same-match` | info | source and target match the same label value |
99
+ | `unused-receiver` | info | receiver defined but not used in any route |
100
+
101
+ ## Tests
102
+
103
+ ```bash
104
+ python3 -m pytest test_linter.py -v
105
+ ```
106
+
107
+ ## License
108
+
109
+ MIT
amlint-0.1.0/README.md ADDED
@@ -0,0 +1,81 @@
1
+ # amlint
2
+
3
+ Semantic linter for Prometheus Alertmanager configs.
4
+
5
+ `amtool check-config` validates syntax. **amlint validates semantics:**
6
+ will alerts actually reach a receiver, does the inhibition rule do anything,
7
+ are there unreachable routing branches? These are the bugs that burn teams —
8
+ config is valid, alerts silently vanish.
9
+
10
+ ## Why
11
+
12
+ Alertmanager configs are YAML routing trees with inhibition rules and receivers.
13
+ The most painful mistakes are syntactically valid:
14
+
15
+ - route references a receiver that doesn't exist → **alerts are dropped**
16
+ - inhibition without `equal` → silences unrelated alerts, you think it's quiet, there's actually a fire
17
+ - catch-all branch before specific ones → specific branches **are unreachable**
18
+ - `match_re` that doesn't compile
19
+ - `group_by` that doesn't behave the way you think
20
+
21
+ `amtool` won't catch any of this. amlint will.
22
+
23
+ ## Install
24
+
25
+ ```bash
26
+ pip install pyyaml
27
+ pip install -e .
28
+ ```
29
+
30
+ ## Usage
31
+
32
+ ```bash
33
+ amlint check alertmanager.yml
34
+ amlint check alertmanager.yml --strict # WARN also exits non-zero
35
+ amlint check alertmanager.yml --format json
36
+ ```
37
+
38
+ Example output:
39
+
40
+ ```
41
+ ERROR Route references receiver 'pager-team' which is not defined in receivers. Alerts matched here will be dropped.
42
+ ↳ route.routes[1] [undefined-receiver]
43
+
44
+ WARN Catch-all route (no matchers) with continue:false will intercept all alerts — 2 subsequent sibling(s) are unreachable.
45
+ ↳ route.routes[0] [unreachable-route]
46
+
47
+ 2 error · 3 warn · 1 info
48
+ ```
49
+
50
+ Exit code `1` on ERROR — ready for CI. `--strict` makes WARN block too.
51
+
52
+ ## CI example
53
+
54
+ ```yaml
55
+ # .github/workflows/lint.yml
56
+ - name: Lint Alertmanager config
57
+ run: amlint check alertmanager.yml --strict
58
+ ```
59
+
60
+ ## Checks
61
+
62
+ | code | level | what it catches |
63
+ |------|-------|-----------------|
64
+ | `undefined-receiver` | error | route references a receiver that doesn't exist |
65
+ | `bad-regex` | error | `match_re` pattern fails to compile |
66
+ | `no-root-route` | error | no root `route` defined |
67
+ | `inhibit-no-equal` | warn | inhibition without `equal` silences too broadly |
68
+ | `unreachable-route` | warn | catch-all hides subsequent sibling routes |
69
+ | `groupby-ellipsis` | warn | `...` mixed with explicit labels in `group_by` |
70
+ | `inhibit-same-match` | info | source and target match the same label value |
71
+ | `unused-receiver` | info | receiver defined but not used in any route |
72
+
73
+ ## Tests
74
+
75
+ ```bash
76
+ python3 -m pytest test_linter.py -v
77
+ ```
78
+
79
+ ## License
80
+
81
+ MIT
File without changes
@@ -0,0 +1,88 @@
1
+ #!/usr/bin/env python3
2
+ """amlint CLI."""
3
+
4
+ import argparse
5
+ import json
6
+ import os
7
+ import sys
8
+
9
+ try:
10
+ import yaml
11
+ except ImportError:
12
+ print("pyyaml is required: pip install pyyaml", file=sys.stderr)
13
+ sys.exit(2)
14
+
15
+ from .linter import lint, ERROR, WARN, INFO
16
+
17
+ _COLOR = sys.stdout.isatty() and os.environ.get("NO_COLOR") is None
18
+
19
+
20
+ def _c(code, s):
21
+ return f"\033[{code}m{s}\033[0m" if _COLOR else s
22
+
23
+
24
+ BADGE = {
25
+ ERROR: _c("31", "ERROR"),
26
+ WARN: _c("33", "WARN "),
27
+ INFO: _c("36", "INFO "),
28
+ }
29
+
30
+
31
+ def load(path):
32
+ if not os.path.exists(path):
33
+ print(f"File not found: {path}", file=sys.stderr)
34
+ sys.exit(2)
35
+ with open(path) as f:
36
+ try:
37
+ return yaml.safe_load(f) or {}
38
+ except yaml.YAMLError as e:
39
+ print(f"Failed to parse YAML: {e}", file=sys.stderr)
40
+ sys.exit(2)
41
+
42
+
43
+ def main(argv=None):
44
+ p = argparse.ArgumentParser(
45
+ prog="amlint",
46
+ description="Semantic linter for Alertmanager configs. Catches what amtool misses.",
47
+ )
48
+ sub = p.add_subparsers(dest="cmd", required=True)
49
+ pc = sub.add_parser("check", help="validate a config file")
50
+ pc.add_argument("file", help="path to alertmanager.yml")
51
+ pc.add_argument("--format", choices=["text", "json"], default="text")
52
+ pc.add_argument("--strict", action="store_true", help="exit non-zero on WARN as well")
53
+
54
+ args = p.parse_args(argv)
55
+ cfg = load(args.file)
56
+ findings = lint(cfg)
57
+
58
+ if args.format == "json":
59
+ print(json.dumps(
60
+ [{"level": f.level, "code": f.code, "message": f.msg, "where": f.where} for f in findings],
61
+ ensure_ascii=False, indent=2,
62
+ ))
63
+ else:
64
+ if not findings:
65
+ print(_c("32", " \u2713 Проблем не знайдено.\n"))
66
+ else:
67
+ print()
68
+ for f in findings:
69
+ loc = _c("2", f" {f.where}") if f.where else ""
70
+ print(f" {BADGE[f.level]} {f.msg}")
71
+ if loc:
72
+ arrow = "\u21b3 "
73
+ print(f" {_c('2', arrow + f.where)} {_c('2', '[' + f.code + ']')}")
74
+ print()
75
+ errs = sum(1 for f in findings if f.level == ERROR)
76
+ warns = sum(1 for f in findings if f.level == WARN)
77
+ infos = sum(1 for f in findings if f.level == INFO)
78
+ print(f" {errs} error \u00b7 {warns} warn \u00b7 {infos} info\n")
79
+
80
+ has_err = any(f.level == ERROR for f in findings)
81
+ has_warn = any(f.level == WARN for f in findings)
82
+ if has_err or (args.strict and has_warn):
83
+ return 1
84
+ return 0
85
+
86
+
87
+ if __name__ == "__main__":
88
+ sys.exit(main())
@@ -0,0 +1,216 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ amlint - semantic linter for Prometheus Alertmanager configs.
4
+
5
+ amtool check-config validates syntax. amlint validates SEMANTICS:
6
+ whether alerts will actually be delivered, whether inhibition rules
7
+ fire correctly, whether routing branches are reachable.
8
+
9
+ Usage:
10
+ amlint check alertmanager.yml
11
+ amlint check alertmanager.yml --format json
12
+ """
13
+
14
+ import re
15
+ import sys
16
+
17
+
18
+ ERROR = "error" # config is broken: alerts will be lost
19
+ WARN = "warn" # almost certainly not what you intended
20
+ INFO = "info" # suspicious, worth a look
21
+
22
+
23
+ class Finding:
24
+ __slots__ = ("level", "code", "msg", "where")
25
+
26
+ def __init__(self, level, code, msg, where=""):
27
+ self.level = level
28
+ self.code = code
29
+ self.msg = msg
30
+ self.where = where
31
+
32
+
33
+ def _defined_receivers(cfg):
34
+ return {r.get("name") for r in cfg.get("receivers", []) if r.get("name")}
35
+
36
+
37
+ def _walk_routes(node, path="route"):
38
+ """Yields (route_node, path) for the root and all nested routes."""
39
+ yield node, path
40
+ for i, child in enumerate(node.get("routes", []) or []):
41
+ yield from _walk_routes(child, f"{path}.routes[{i}]")
42
+
43
+
44
+ # CHECK 1: route references a receiver that doesn't exist
45
+ def check_undefined_receivers(cfg):
46
+ out = []
47
+ defined = _defined_receivers(cfg)
48
+ route = cfg.get("route")
49
+ if not route:
50
+ out.append(Finding(ERROR, "no-root-route", "No root 'route' defined.", "route"))
51
+ return out
52
+ for node, path in _walk_routes(route):
53
+ rcv = node.get("receiver")
54
+ if rcv and rcv not in defined:
55
+ out.append(Finding(
56
+ ERROR, "undefined-receiver",
57
+ f"Route references receiver '{rcv}' which is not defined in receivers. "
58
+ f"Alerts matched here will be dropped.",
59
+ path,
60
+ ))
61
+ return out
62
+
63
+
64
+ # CHECK 2: receiver is defined but never used in any route
65
+ def check_unused_receivers(cfg):
66
+ out = []
67
+ defined = _defined_receivers(cfg)
68
+ used = set()
69
+ route = cfg.get("route")
70
+ if route:
71
+ for node, _ in _walk_routes(route):
72
+ if node.get("receiver"):
73
+ used.add(node["receiver"])
74
+ for name in defined - used:
75
+ out.append(Finding(
76
+ INFO, "unused-receiver",
77
+ f"Receiver '{name}' is defined but not referenced by any route.",
78
+ "receivers",
79
+ ))
80
+ return out
81
+
82
+
83
+ # CHECK 3: inhibition rule that can never fire
84
+ def check_dead_inhibitions(cfg):
85
+ """
86
+ An inhibition rule silences target alerts when a source alert fires,
87
+ but only when labels in 'equal' match. Missing 'equal' means the rule
88
+ will silence across unrelated alerts — almost never intentional.
89
+ """
90
+ out = []
91
+ for i, rule in enumerate(cfg.get("inhibit_rules", []) or []):
92
+ where = f"inhibit_rules[{i}]"
93
+ src = {**(rule.get("source_match") or {}), **(rule.get("source_matchers_map") or {})}
94
+ tgt = {**(rule.get("target_match") or {}), **(rule.get("target_matchers_map") or {})}
95
+ equal = rule.get("equal", []) or []
96
+
97
+ if not equal and (src or tgt):
98
+ out.append(Finding(
99
+ WARN, "inhibit-no-equal",
100
+ "Inhibition rule has no 'equal' field: it will silence alerts across "
101
+ "unrelated firing sources (no shared label binding them). Almost always a mistake.",
102
+ where,
103
+ ))
104
+
105
+ for key in set(src) & set(tgt):
106
+ if src[key] == tgt[key]:
107
+ out.append(Finding(
108
+ INFO, "inhibit-same-match",
109
+ f"source_match and target_match both have '{key}={src[key]}'. "
110
+ f"Check that the rule is not silencing its own source alert.",
111
+ where,
112
+ ))
113
+ return out
114
+
115
+
116
+ # CHECK 4: match_re / matchers: patterns that fail to compile
117
+ _MATCHER_RE = re.compile(r'^([^=!~]+)(=~|!~)(.+)$')
118
+
119
+
120
+ def _regex_patterns(node):
121
+ """Yield (label, pattern) for all regex matchers in a route node."""
122
+ for label, pattern in (node.get("match_re") or {}).items():
123
+ yield label, pattern
124
+ for m in node.get("matchers") or []:
125
+ hit = _MATCHER_RE.match(str(m))
126
+ if hit:
127
+ yield hit.group(1).strip(), hit.group(3)
128
+
129
+
130
+ def check_bad_regex(cfg):
131
+ out = []
132
+ route = cfg.get("route")
133
+ if not route:
134
+ return out
135
+ for node, path in _walk_routes(route):
136
+ for label, pattern in _regex_patterns(node):
137
+ try:
138
+ re.compile(pattern)
139
+ except re.error as e:
140
+ out.append(Finding(
141
+ ERROR, "bad-regex",
142
+ f"Regex for '{label}' does not compile: {e}",
143
+ path,
144
+ ))
145
+ return out
146
+
147
+
148
+ # CHECK 5: unreachable sibling routes behind a catch-all
149
+ def check_unreachable_routes(cfg):
150
+ """
151
+ Alertmanager evaluates sibling routes in order. If a route matches
152
+ and continue is not true, subsequent siblings won't receive the alert.
153
+ A catch-all route (no matchers) with continue:false makes all following
154
+ siblings unreachable.
155
+ """
156
+ out = []
157
+ route = cfg.get("route")
158
+ if not route:
159
+ return out
160
+
161
+ def scan_siblings(routes, parent_path):
162
+ for i, child in enumerate(routes or []):
163
+ path = f"{parent_path}.routes[{i}]"
164
+ has_matcher = bool(
165
+ child.get("match") or child.get("match_re") or child.get("matchers")
166
+ )
167
+ is_catch_all = not has_matcher
168
+ cont = child.get("continue", False)
169
+ if is_catch_all and not cont and i < len(routes) - 1:
170
+ out.append(Finding(
171
+ WARN, "unreachable-route",
172
+ f"Catch-all route (no matchers) with continue:false will intercept all alerts "
173
+ f"— {len(routes) - i - 1} subsequent sibling(s) are unreachable.",
174
+ path,
175
+ ))
176
+ scan_siblings(child.get("routes"), path)
177
+
178
+ scan_siblings(route.get("routes"), "route")
179
+ return out
180
+
181
+
182
+ # CHECK 6: group_by with '...' mixed with explicit labels
183
+ def check_groupby(cfg):
184
+ out = []
185
+ route = cfg.get("route")
186
+ if not route:
187
+ return out
188
+ for node, path in _walk_routes(route):
189
+ gb = node.get("group_by") or []
190
+ if "..." in gb and len(gb) > 1:
191
+ out.append(Finding(
192
+ WARN, "groupby-ellipsis",
193
+ "group_by contains '...' alongside other labels — '...' already groups by all "
194
+ "labels, making the others redundant. Remove either '...' or the explicit labels.",
195
+ path,
196
+ ))
197
+ return out
198
+
199
+
200
+ ALL_CHECKS = [
201
+ check_undefined_receivers,
202
+ check_unused_receivers,
203
+ check_dead_inhibitions,
204
+ check_bad_regex,
205
+ check_unreachable_routes,
206
+ check_groupby,
207
+ ]
208
+
209
+
210
+ def lint(cfg):
211
+ findings = []
212
+ for check in ALL_CHECKS:
213
+ findings.extend(check(cfg))
214
+ order = {ERROR: 0, WARN: 1, INFO: 2}
215
+ findings.sort(key=lambda f: order[f.level])
216
+ return findings
@@ -0,0 +1,109 @@
1
+ Metadata-Version: 2.4
2
+ Name: amlint
3
+ Version: 0.1.0
4
+ Summary: Semantic linter for Prometheus Alertmanager configs
5
+ Author: danikdanik2013
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/danikdanik2013/amlint
8
+ Project-URL: Repository, https://github.com/danikdanik2013/amlint
9
+ Project-URL: Bug Tracker, https://github.com/danikdanik2013/amlint/issues
10
+ Keywords: alertmanager,prometheus,linter,devops,monitoring
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Environment :: Console
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Intended Audience :: System Administrators
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.9
18
+ Classifier: Programming Language :: Python :: 3.10
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Topic :: System :: Monitoring
22
+ Classifier: Topic :: Utilities
23
+ Requires-Python: >=3.9
24
+ Description-Content-Type: text/markdown
25
+ Requires-Dist: pyyaml>=5.1
26
+ Provides-Extra: dev
27
+ Requires-Dist: pytest>=7; extra == "dev"
28
+
29
+ # amlint
30
+
31
+ Semantic linter for Prometheus Alertmanager configs.
32
+
33
+ `amtool check-config` validates syntax. **amlint validates semantics:**
34
+ will alerts actually reach a receiver, does the inhibition rule do anything,
35
+ are there unreachable routing branches? These are the bugs that burn teams —
36
+ config is valid, alerts silently vanish.
37
+
38
+ ## Why
39
+
40
+ Alertmanager configs are YAML routing trees with inhibition rules and receivers.
41
+ The most painful mistakes are syntactically valid:
42
+
43
+ - route references a receiver that doesn't exist → **alerts are dropped**
44
+ - inhibition without `equal` → silences unrelated alerts, you think it's quiet, there's actually a fire
45
+ - catch-all branch before specific ones → specific branches **are unreachable**
46
+ - `match_re` that doesn't compile
47
+ - `group_by` that doesn't behave the way you think
48
+
49
+ `amtool` won't catch any of this. amlint will.
50
+
51
+ ## Install
52
+
53
+ ```bash
54
+ pip install pyyaml
55
+ pip install -e .
56
+ ```
57
+
58
+ ## Usage
59
+
60
+ ```bash
61
+ amlint check alertmanager.yml
62
+ amlint check alertmanager.yml --strict # WARN also exits non-zero
63
+ amlint check alertmanager.yml --format json
64
+ ```
65
+
66
+ Example output:
67
+
68
+ ```
69
+ ERROR Route references receiver 'pager-team' which is not defined in receivers. Alerts matched here will be dropped.
70
+ ↳ route.routes[1] [undefined-receiver]
71
+
72
+ WARN Catch-all route (no matchers) with continue:false will intercept all alerts — 2 subsequent sibling(s) are unreachable.
73
+ ↳ route.routes[0] [unreachable-route]
74
+
75
+ 2 error · 3 warn · 1 info
76
+ ```
77
+
78
+ Exit code `1` on ERROR — ready for CI. `--strict` makes WARN block too.
79
+
80
+ ## CI example
81
+
82
+ ```yaml
83
+ # .github/workflows/lint.yml
84
+ - name: Lint Alertmanager config
85
+ run: amlint check alertmanager.yml --strict
86
+ ```
87
+
88
+ ## Checks
89
+
90
+ | code | level | what it catches |
91
+ |------|-------|-----------------|
92
+ | `undefined-receiver` | error | route references a receiver that doesn't exist |
93
+ | `bad-regex` | error | `match_re` pattern fails to compile |
94
+ | `no-root-route` | error | no root `route` defined |
95
+ | `inhibit-no-equal` | warn | inhibition without `equal` silences too broadly |
96
+ | `unreachable-route` | warn | catch-all hides subsequent sibling routes |
97
+ | `groupby-ellipsis` | warn | `...` mixed with explicit labels in `group_by` |
98
+ | `inhibit-same-match` | info | source and target match the same label value |
99
+ | `unused-receiver` | info | receiver defined but not used in any route |
100
+
101
+ ## Tests
102
+
103
+ ```bash
104
+ python3 -m pytest test_linter.py -v
105
+ ```
106
+
107
+ ## License
108
+
109
+ MIT
@@ -0,0 +1,11 @@
1
+ README.md
2
+ pyproject.toml
3
+ amlint/__init__.py
4
+ amlint/cli.py
5
+ amlint/linter.py
6
+ amlint.egg-info/PKG-INFO
7
+ amlint.egg-info/SOURCES.txt
8
+ amlint.egg-info/dependency_links.txt
9
+ amlint.egg-info/entry_points.txt
10
+ amlint.egg-info/requires.txt
11
+ amlint.egg-info/top_level.txt
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ amlint = amlint.cli:main
@@ -0,0 +1,4 @@
1
+ pyyaml>=5.1
2
+
3
+ [dev]
4
+ pytest>=7
@@ -0,0 +1 @@
1
+ amlint
@@ -0,0 +1,39 @@
1
+ [project]
2
+ name = "amlint"
3
+ version = "0.1.0"
4
+ description = "Semantic linter for Prometheus Alertmanager configs"
5
+ readme = "README.md"
6
+ requires-python = ">=3.9"
7
+ license = {text = "MIT"}
8
+ authors = [{name = "danikdanik2013"}]
9
+ keywords = ["alertmanager", "prometheus", "linter", "devops", "monitoring"]
10
+ classifiers = [
11
+ "Development Status :: 4 - Beta",
12
+ "Environment :: Console",
13
+ "Intended Audience :: Developers",
14
+ "Intended Audience :: System Administrators",
15
+ "License :: OSI Approved :: MIT License",
16
+ "Programming Language :: Python :: 3",
17
+ "Programming Language :: Python :: 3.9",
18
+ "Programming Language :: Python :: 3.10",
19
+ "Programming Language :: Python :: 3.11",
20
+ "Programming Language :: Python :: 3.12",
21
+ "Topic :: System :: Monitoring",
22
+ "Topic :: Utilities",
23
+ ]
24
+ dependencies = ["pyyaml>=5.1"]
25
+
26
+ [project.optional-dependencies]
27
+ dev = ["pytest>=7"]
28
+
29
+ [project.urls]
30
+ Homepage = "https://github.com/danikdanik2013/amlint"
31
+ Repository = "https://github.com/danikdanik2013/amlint"
32
+ "Bug Tracker" = "https://github.com/danikdanik2013/amlint/issues"
33
+
34
+ [project.scripts]
35
+ amlint = "amlint.cli:main"
36
+
37
+ [build-system]
38
+ requires = ["setuptools>=61", "wheel"]
39
+ build-backend = "setuptools.build_meta"
amlint-0.1.0/setup.cfg ADDED
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+