promtext-cli 0.1.2.dev97__tar.gz → 0.1.2.dev100__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.
- {promtext_cli-0.1.2.dev97 → promtext_cli-0.1.2.dev100}/.copier-answers.yml +2 -2
- {promtext_cli-0.1.2.dev97 → promtext_cli-0.1.2.dev100}/.pre-commit-config.yaml +1 -1
- {promtext_cli-0.1.2.dev97 → promtext_cli-0.1.2.dev100}/PKG-INFO +1 -1
- promtext_cli-0.1.2.dev100/promtext_cli/main.py +12 -0
- promtext_cli-0.1.2.dev100/promtext_cli/promtext.py +184 -0
- {promtext_cli-0.1.2.dev97 → promtext_cli-0.1.2.dev100}/pyproject.toml +24 -2
- {promtext_cli-0.1.2.dev97 → promtext_cli-0.1.2.dev100}/tests/test_blackbox.py +6 -13
- {promtext_cli-0.1.2.dev97 → promtext_cli-0.1.2.dev100}/tests/test_functional.py +52 -58
- promtext_cli-0.1.2.dev97/promtext_cli/main.py +0 -152
- {promtext_cli-0.1.2.dev97 → promtext_cli-0.1.2.dev100}/.commitlintrc.yaml +0 -0
- {promtext_cli-0.1.2.dev97 → promtext_cli-0.1.2.dev100}/.forgejo/workflows/commitlint.yaml +0 -0
- {promtext_cli-0.1.2.dev97 → promtext_cli-0.1.2.dev100}/.forgejo/workflows/lint.yaml +0 -0
- {promtext_cli-0.1.2.dev97 → promtext_cli-0.1.2.dev100}/.forgejo/workflows/publish.yaml +0 -0
- {promtext_cli-0.1.2.dev97 → promtext_cli-0.1.2.dev100}/.forgejo/workflows/test.yaml +0 -0
- {promtext_cli-0.1.2.dev97 → promtext_cli-0.1.2.dev100}/.gitignore +0 -0
- {promtext_cli-0.1.2.dev97 → promtext_cli-0.1.2.dev100}/README.md +0 -0
- {promtext_cli-0.1.2.dev97 → promtext_cli-0.1.2.dev100}/cliff.toml +0 -0
- {promtext_cli-0.1.2.dev97 → promtext_cli-0.1.2.dev100}/promtext_cli/__init__.py +0 -0
- {promtext_cli-0.1.2.dev97 → promtext_cli-0.1.2.dev100}/renovate.json +0 -0
- {promtext_cli-0.1.2.dev97 → promtext_cli-0.1.2.dev100}/tests/.gitkeep +0 -0
- {promtext_cli-0.1.2.dev97 → promtext_cli-0.1.2.dev100}/uv.lock +0 -0
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
# Changes here will be overwritten by Copier.
|
|
2
|
-
_commit: 0.
|
|
2
|
+
_commit: 0.9.0
|
|
3
3
|
_src_path: https://codeberg.org/margau/copier-python-uv.git
|
|
4
4
|
code_dir: promtext_cli
|
|
5
5
|
container: false
|
|
@@ -14,4 +14,4 @@ project_name: promtext-cli
|
|
|
14
14
|
pypi_publish: true
|
|
15
15
|
pypi_publish_dev: true
|
|
16
16
|
scripts:
|
|
17
|
-
promtext: promtext_cli.main
|
|
17
|
+
promtext: promtext_cli:main.main
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: promtext-cli
|
|
3
|
-
Version: 0.1.2.
|
|
3
|
+
Version: 0.1.2.dev100
|
|
4
4
|
Summary: Prometheus Textfile Tooling
|
|
5
5
|
Project-URL: Documentation, https://codeberg.org/margau/promtext-cli/src/branch/main#readme
|
|
6
6
|
Project-URL: Issues, https://codeberg.org/margau/promtext-cli/issues
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
"""module providing the core functionality"""
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
import logging
|
|
5
|
+
import sys
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
from prometheus_client import CollectorRegistry, Gauge, write_to_textfile
|
|
9
|
+
from prometheus_client.parser import text_string_to_metric_families
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class Promtext:
|
|
13
|
+
"""Class for the core functionality"""
|
|
14
|
+
|
|
15
|
+
def __init__(self):
|
|
16
|
+
self.textfile = None
|
|
17
|
+
self.logger = None
|
|
18
|
+
self.args = None
|
|
19
|
+
self.registry = CollectorRegistry()
|
|
20
|
+
|
|
21
|
+
self.metrics = {}
|
|
22
|
+
|
|
23
|
+
def _arguments(self):
|
|
24
|
+
"""Load every input item from arguments & define cli"""
|
|
25
|
+
# required file first
|
|
26
|
+
parser = argparse.ArgumentParser(description="Prometheus textfile helper")
|
|
27
|
+
parser.add_argument(
|
|
28
|
+
"filename",
|
|
29
|
+
type=str,
|
|
30
|
+
help="Path to existing or new prometheus textfile, will be updated",
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
# metric name, required
|
|
34
|
+
parser.add_argument("metric", type=str, help="metric name (new or updated)")
|
|
35
|
+
|
|
36
|
+
# metric value as int/float, required
|
|
37
|
+
parser.add_argument("value", type=float, help="metric value")
|
|
38
|
+
|
|
39
|
+
# metric documentation as optional argument
|
|
40
|
+
parser.add_argument(
|
|
41
|
+
"--docs",
|
|
42
|
+
type=str,
|
|
43
|
+
help="metric documentation",
|
|
44
|
+
default="metric appended by promtext-cli",
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
# labels, key-value, minimum 0, repeatable
|
|
48
|
+
parser.add_argument(
|
|
49
|
+
"--label",
|
|
50
|
+
metavar="KEY=VALUE",
|
|
51
|
+
help="label key=value pairs",
|
|
52
|
+
action="append",
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
# log level from argparse
|
|
56
|
+
parser.add_argument(
|
|
57
|
+
"-v",
|
|
58
|
+
"--verbose",
|
|
59
|
+
action="store_const",
|
|
60
|
+
dest="loglevel",
|
|
61
|
+
const=logging.INFO,
|
|
62
|
+
)
|
|
63
|
+
self.args = parser.parse_args()
|
|
64
|
+
|
|
65
|
+
# processing: check if file is available, if yes, parse
|
|
66
|
+
self.textfile = Path(self.args.filename)
|
|
67
|
+
|
|
68
|
+
def parse_file(self):
|
|
69
|
+
"""If possible, convert the input textfile to metrics in the registry"""
|
|
70
|
+
# check if self.args.filename exists with pathlib
|
|
71
|
+
if self.textfile.is_file():
|
|
72
|
+
for f in text_string_to_metric_families(
|
|
73
|
+
self.textfile.read_text(encoding="utf-8"),
|
|
74
|
+
):
|
|
75
|
+
# per metric: iterate over samples, create metric in registry
|
|
76
|
+
m = False
|
|
77
|
+
samples = list(f.samples)
|
|
78
|
+
if len(samples) > 0:
|
|
79
|
+
# if we have samples, use the labelnames from them
|
|
80
|
+
labelnames = list(samples[0].labels.keys())
|
|
81
|
+
# metric-type specific init
|
|
82
|
+
if f.type == "gauge":
|
|
83
|
+
m = Gauge(
|
|
84
|
+
f.name,
|
|
85
|
+
f.documentation,
|
|
86
|
+
unit=f.unit,
|
|
87
|
+
labelnames=labelnames,
|
|
88
|
+
registry=self.registry,
|
|
89
|
+
)
|
|
90
|
+
else:
|
|
91
|
+
# we don't support other types yet, continue in these cases
|
|
92
|
+
self.logger.warning(
|
|
93
|
+
"unsupported metric type %s, dropping",
|
|
94
|
+
f.type,
|
|
95
|
+
)
|
|
96
|
+
continue
|
|
97
|
+
for s in samples:
|
|
98
|
+
if len(labelnames) > 0:
|
|
99
|
+
labelvalues = list(s.labels.values())
|
|
100
|
+
m.labels(*labelvalues).set(s.value)
|
|
101
|
+
else:
|
|
102
|
+
m.set(s.value)
|
|
103
|
+
self.metrics[f.name] = m
|
|
104
|
+
self.logger.info(
|
|
105
|
+
"copy gauge metric %s with labels %s from old file",
|
|
106
|
+
f.name,
|
|
107
|
+
", ".join(labelnames),
|
|
108
|
+
)
|
|
109
|
+
else:
|
|
110
|
+
self.logger.warning(
|
|
111
|
+
"got empty metric %s from old file, dropping",
|
|
112
|
+
f.name,
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
def _build_metrics(self):
|
|
116
|
+
# add new metric from commandline
|
|
117
|
+
m = False
|
|
118
|
+
|
|
119
|
+
# figure out labelkey- and values
|
|
120
|
+
labels = {}
|
|
121
|
+
if self.args.label:
|
|
122
|
+
for lpair in self.args.label:
|
|
123
|
+
k, v = lpair.split("=")
|
|
124
|
+
labels[k] = v
|
|
125
|
+
|
|
126
|
+
labelvalues = []
|
|
127
|
+
# here, we use a new metric
|
|
128
|
+
if self.args.metric not in self.metrics:
|
|
129
|
+
self.logger.info("adding new metric %s", self.args.metric)
|
|
130
|
+
m = Gauge(
|
|
131
|
+
self.args.metric,
|
|
132
|
+
self.args.docs,
|
|
133
|
+
registry=self.registry,
|
|
134
|
+
labelnames=labels.keys(),
|
|
135
|
+
)
|
|
136
|
+
labelvalues = labels.values()
|
|
137
|
+
else:
|
|
138
|
+
m = self.metrics[self.args.metric]
|
|
139
|
+
|
|
140
|
+
# There is no way to access existing labelnames directly
|
|
141
|
+
# pylint: disable=W0212
|
|
142
|
+
old_labelnames = list(m._labelnames) # noqa: SLF001
|
|
143
|
+
for la in old_labelnames:
|
|
144
|
+
self.logger.info("processing label %s", la)
|
|
145
|
+
if la in labels: # labelvalues are needed in order!
|
|
146
|
+
labelvalues.append(labels[la])
|
|
147
|
+
else:
|
|
148
|
+
self.logger.error(
|
|
149
|
+
"previously known label '%s' missing, cannot update!",
|
|
150
|
+
la,
|
|
151
|
+
)
|
|
152
|
+
sys.exit(1)
|
|
153
|
+
if len(old_labelnames) != len(labels.keys()):
|
|
154
|
+
self.logger.error(
|
|
155
|
+
"""labelnames for metric %s not the same, cannot update!
|
|
156
|
+
Old: %s, New: %s""",
|
|
157
|
+
self.args.metric,
|
|
158
|
+
old_labelnames,
|
|
159
|
+
list(labels.keys()),
|
|
160
|
+
)
|
|
161
|
+
sys.exit(1)
|
|
162
|
+
self.logger.info("updating metric %s", self.args.metric)
|
|
163
|
+
|
|
164
|
+
# actually set the value
|
|
165
|
+
if len(labelvalues) > 0:
|
|
166
|
+
m.labels(*labelvalues).set(self.args.value)
|
|
167
|
+
else:
|
|
168
|
+
m.set(self.args.value)
|
|
169
|
+
|
|
170
|
+
def output_file(self):
|
|
171
|
+
"""Output to a textfile"""
|
|
172
|
+
write_to_textfile(self.textfile, self.registry)
|
|
173
|
+
self.logger.info("wrote to %s", self.textfile)
|
|
174
|
+
|
|
175
|
+
def cli_entrypoint(self):
|
|
176
|
+
"""Main method called from the CLI"""
|
|
177
|
+
self._arguments()
|
|
178
|
+
|
|
179
|
+
logging.basicConfig(level=self.args.loglevel)
|
|
180
|
+
self.logger = logging.getLogger(__name__)
|
|
181
|
+
|
|
182
|
+
self.parse_file()
|
|
183
|
+
self._build_metrics()
|
|
184
|
+
self.output_file()
|
|
@@ -28,7 +28,7 @@ Source = "https://codeberg.org/margau/promtext-cli.git"
|
|
|
28
28
|
|
|
29
29
|
# Scripts
|
|
30
30
|
[project.scripts]
|
|
31
|
-
promtext = "promtext_cli.main
|
|
31
|
+
promtext = "promtext_cli:main.main"
|
|
32
32
|
|
|
33
33
|
[dependency-groups]
|
|
34
34
|
dev = [
|
|
@@ -43,6 +43,28 @@ dev = [
|
|
|
43
43
|
cache-keys = [{ file = "pyproject.toml" }, { git = { commit = true, tags = true } }]
|
|
44
44
|
|
|
45
45
|
[tool.coverage.run]
|
|
46
|
-
source = ["
|
|
46
|
+
source = ["promtext_cli", "tests"]
|
|
47
|
+
|
|
48
|
+
[tool.ruff.lint]
|
|
49
|
+
select = ["ALL"]
|
|
50
|
+
ignore = [
|
|
51
|
+
"D104",
|
|
52
|
+
"D107",
|
|
53
|
+
"D200",
|
|
54
|
+
"D203",
|
|
55
|
+
"D205",
|
|
56
|
+
"D213",
|
|
57
|
+
"D400",
|
|
58
|
+
"D401",
|
|
59
|
+
"D415", # TODO: Remove later
|
|
60
|
+
"FIX002", # TODO: Remove later
|
|
61
|
+
"TD002", # TODO: Remove later
|
|
62
|
+
"TD003", # TODO: Remove later
|
|
63
|
+
"ANN", # TODO: Remove later
|
|
64
|
+
"COM812",
|
|
65
|
+
]
|
|
66
|
+
|
|
67
|
+
[tool.ruff.lint.per-file-ignores]
|
|
68
|
+
"tests/**.py" = ["S101", "INP001", "PLR2004"]
|
|
47
69
|
|
|
48
70
|
# TODO: Dev/CI scripts for lint & co.
|
|
@@ -1,39 +1,32 @@
|
|
|
1
1
|
# pylint: disable=R0801
|
|
2
|
-
"""
|
|
2
|
+
"""Blackbox-like testing of the promtext cli tool"""
|
|
3
3
|
|
|
4
4
|
import shutil
|
|
5
|
+
|
|
5
6
|
from cli_test_helpers import shell
|
|
6
7
|
|
|
7
8
|
|
|
8
9
|
def test_entrypoint():
|
|
9
|
-
"""
|
|
10
|
-
Is entrypoint script installed? (pyproject.toml)
|
|
11
|
-
"""
|
|
10
|
+
"""Is entrypoint script installed? (pyproject.toml)"""
|
|
12
11
|
assert shutil.which("promtext")
|
|
13
12
|
|
|
14
13
|
|
|
15
14
|
def test_help():
|
|
16
|
-
"""
|
|
17
|
-
Does the help command work?
|
|
18
|
-
"""
|
|
15
|
+
"""Does the help command work?"""
|
|
19
16
|
result = shell("promtext --help")
|
|
20
17
|
assert result.exit_code == 0
|
|
21
18
|
assert "usage:" in result.stdout
|
|
22
19
|
|
|
23
20
|
|
|
24
21
|
def test_empty():
|
|
25
|
-
"""
|
|
26
|
-
Does the command fails with no arguments?
|
|
27
|
-
"""
|
|
22
|
+
"""Does the command fails with no arguments?"""
|
|
28
23
|
result = shell("promtext")
|
|
29
24
|
assert result.exit_code == 2
|
|
30
25
|
assert "usage:" in result.stderr
|
|
31
26
|
|
|
32
27
|
|
|
33
28
|
def test_new_file(tmp_path):
|
|
34
|
-
"""
|
|
35
|
-
Does the command create a new file with metrics content?
|
|
36
|
-
"""
|
|
29
|
+
"""Does the command create a new file with metrics content?"""
|
|
37
30
|
promfile = tmp_path / "new_file.prom"
|
|
38
31
|
result = shell(f"promtext {promfile} test_metric 0")
|
|
39
32
|
assert result.exit_code == 0
|
|
@@ -1,19 +1,17 @@
|
|
|
1
1
|
# pylint: disable=R0801,W0511
|
|
2
|
-
"""
|
|
2
|
+
"""Functional testing by calling the methods directly"""
|
|
3
3
|
|
|
4
|
-
from cli_test_helpers import ArgvContext
|
|
5
4
|
import pytest
|
|
5
|
+
from cli_test_helpers import ArgvContext
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
from promtext_cli.main import main as promtext_main
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
def test_new_file(tmp_path):
|
|
11
|
-
"""
|
|
12
|
-
Does the command create a new file with metrics content?
|
|
13
|
-
"""
|
|
11
|
+
"""Does the command create a new file with metrics content?"""
|
|
14
12
|
promfile = tmp_path / "new_file.prom"
|
|
15
13
|
with ArgvContext("promtext", str(promfile), "test_metric", "0"):
|
|
16
|
-
|
|
14
|
+
promtext_main()
|
|
17
15
|
assert promfile.exists()
|
|
18
16
|
assert promfile.read_text() == (
|
|
19
17
|
"# HELP test_metric metric appended by promtext-cli"
|
|
@@ -26,14 +24,17 @@ def test_new_file(tmp_path):
|
|
|
26
24
|
|
|
27
25
|
|
|
28
26
|
def test_labels(tmp_path):
|
|
29
|
-
"""
|
|
30
|
-
Does the command create a new file with metrics content?
|
|
31
|
-
"""
|
|
27
|
+
"""Does the command create a new file with metrics content?"""
|
|
32
28
|
promfile = tmp_path / "label_file.prom"
|
|
33
29
|
with ArgvContext(
|
|
34
|
-
"promtext",
|
|
30
|
+
"promtext",
|
|
31
|
+
"--label",
|
|
32
|
+
"testlabel=testvalue",
|
|
33
|
+
str(promfile),
|
|
34
|
+
"test_metric",
|
|
35
|
+
0,
|
|
35
36
|
):
|
|
36
|
-
|
|
37
|
+
promtext_main()
|
|
37
38
|
assert promfile.exists()
|
|
38
39
|
assert promfile.read_text() == (
|
|
39
40
|
"# HELP test_metric metric appended by promtext-cli"
|
|
@@ -46,8 +47,7 @@ def test_labels(tmp_path):
|
|
|
46
47
|
|
|
47
48
|
|
|
48
49
|
def test_existing_metric_append_metric(tmp_path):
|
|
49
|
-
"""
|
|
50
|
-
Does the command append a new metric to an existing file
|
|
50
|
+
"""Does the command append a new metric to an existing file
|
|
51
51
|
with metrics content and preserve existing metrics?
|
|
52
52
|
"""
|
|
53
53
|
promfile = tmp_path / "existing_file.prom"
|
|
@@ -57,12 +57,17 @@ def test_existing_metric_append_metric(tmp_path):
|
|
|
57
57
|
"# TYPE existing_metric gauge"
|
|
58
58
|
"\n"
|
|
59
59
|
'existing_metric{testlabel="testvalue"} 0.0'
|
|
60
|
-
"\n"
|
|
60
|
+
"\n",
|
|
61
61
|
)
|
|
62
62
|
with ArgvContext(
|
|
63
|
-
"promtext",
|
|
63
|
+
"promtext",
|
|
64
|
+
"--label",
|
|
65
|
+
"testlabel=testvalue",
|
|
66
|
+
str(promfile),
|
|
67
|
+
"new_metric",
|
|
68
|
+
0,
|
|
64
69
|
):
|
|
65
|
-
|
|
70
|
+
promtext_main()
|
|
66
71
|
assert promfile.exists()
|
|
67
72
|
assert promfile.read_text() == (
|
|
68
73
|
"# HELP existing_metric metric appended by promtext-cli"
|
|
@@ -81,8 +86,7 @@ def test_existing_metric_append_metric(tmp_path):
|
|
|
81
86
|
|
|
82
87
|
|
|
83
88
|
def test_existing_metric_append_labelvalue(tmp_path):
|
|
84
|
-
"""
|
|
85
|
-
Does the command append a new metric to an existing file
|
|
89
|
+
"""Does the command append a new metric to an existing file
|
|
86
90
|
with metrics content and preserve existing metrics?
|
|
87
91
|
"""
|
|
88
92
|
promfile = tmp_path / "existing_file.prom"
|
|
@@ -92,12 +96,17 @@ def test_existing_metric_append_labelvalue(tmp_path):
|
|
|
92
96
|
"# TYPE existing_metric gauge"
|
|
93
97
|
"\n"
|
|
94
98
|
'existing_metric{testlabel="existing"} 0.0'
|
|
95
|
-
"\n"
|
|
99
|
+
"\n",
|
|
96
100
|
)
|
|
97
101
|
with ArgvContext(
|
|
98
|
-
"promtext",
|
|
102
|
+
"promtext",
|
|
103
|
+
"--label",
|
|
104
|
+
"testlabel=new",
|
|
105
|
+
str(promfile),
|
|
106
|
+
"existing_metric",
|
|
107
|
+
0,
|
|
99
108
|
):
|
|
100
|
-
|
|
109
|
+
promtext_main()
|
|
101
110
|
assert promfile.exists()
|
|
102
111
|
assert promfile.read_text() == (
|
|
103
112
|
"# HELP existing_metric metric appended by promtext-cli"
|
|
@@ -112,8 +121,7 @@ def test_existing_metric_append_labelvalue(tmp_path):
|
|
|
112
121
|
|
|
113
122
|
|
|
114
123
|
def test_existing_metric_multilabel(tmp_path):
|
|
115
|
-
"""
|
|
116
|
-
Does the command append a new metric to an existing file
|
|
124
|
+
"""Does the command append a new metric to an existing file
|
|
117
125
|
with metrics content and preserve existing metrics?
|
|
118
126
|
"""
|
|
119
127
|
promfile = tmp_path / "existing_file.prom"
|
|
@@ -123,7 +131,7 @@ def test_existing_metric_multilabel(tmp_path):
|
|
|
123
131
|
"# TYPE existing_metric gauge"
|
|
124
132
|
"\n"
|
|
125
133
|
'existing_metric{first="existing", second="existing"} 0.0'
|
|
126
|
-
"\n"
|
|
134
|
+
"\n",
|
|
127
135
|
)
|
|
128
136
|
with ArgvContext(
|
|
129
137
|
"promtext",
|
|
@@ -135,7 +143,7 @@ def test_existing_metric_multilabel(tmp_path):
|
|
|
135
143
|
"existing_metric",
|
|
136
144
|
"42",
|
|
137
145
|
):
|
|
138
|
-
|
|
146
|
+
promtext_main()
|
|
139
147
|
assert promfile.exists()
|
|
140
148
|
assert promfile.read_text() == (
|
|
141
149
|
"# HELP existing_metric metric appended by promtext-cli"
|
|
@@ -150,9 +158,7 @@ def test_existing_metric_multilabel(tmp_path):
|
|
|
150
158
|
|
|
151
159
|
|
|
152
160
|
def test_existing_metric_overwrite(tmp_path):
|
|
153
|
-
"""
|
|
154
|
-
Does the command overwrite a metric/labelset
|
|
155
|
-
"""
|
|
161
|
+
"""Does the command overwrite a metric/labelset"""
|
|
156
162
|
promfile = tmp_path / "existing_file.prom"
|
|
157
163
|
promfile.write_text(
|
|
158
164
|
"# HELP existing_metric metric appended by promtext-cli"
|
|
@@ -160,7 +166,7 @@ def test_existing_metric_overwrite(tmp_path):
|
|
|
160
166
|
"# TYPE existing_metric gauge"
|
|
161
167
|
"\n"
|
|
162
168
|
'existing_metric{testlabel="existing"} 0.0'
|
|
163
|
-
"\n"
|
|
169
|
+
"\n",
|
|
164
170
|
)
|
|
165
171
|
with ArgvContext(
|
|
166
172
|
"promtext",
|
|
@@ -170,7 +176,7 @@ def test_existing_metric_overwrite(tmp_path):
|
|
|
170
176
|
"existing_metric",
|
|
171
177
|
"42",
|
|
172
178
|
):
|
|
173
|
-
|
|
179
|
+
promtext_main()
|
|
174
180
|
assert promfile.exists()
|
|
175
181
|
assert promfile.read_text() == (
|
|
176
182
|
"# HELP existing_metric metric appended by promtext-cli"
|
|
@@ -183,9 +189,7 @@ def test_existing_metric_overwrite(tmp_path):
|
|
|
183
189
|
|
|
184
190
|
|
|
185
191
|
def test_existing_metric_plain(tmp_path):
|
|
186
|
-
"""
|
|
187
|
-
Does the command overwrite a metric without a label?
|
|
188
|
-
"""
|
|
192
|
+
"""Does the command overwrite a metric without a label?"""
|
|
189
193
|
promfile = tmp_path / "existing_file.prom"
|
|
190
194
|
promfile.write_text(
|
|
191
195
|
"# HELP existing_metric metric appended by promtext-cli"
|
|
@@ -193,10 +197,10 @@ def test_existing_metric_plain(tmp_path):
|
|
|
193
197
|
"# TYPE existing_metric gauge"
|
|
194
198
|
"\n"
|
|
195
199
|
"existing_metric 0.0"
|
|
196
|
-
"\n"
|
|
200
|
+
"\n",
|
|
197
201
|
)
|
|
198
202
|
with ArgvContext("promtext", str(promfile), "existing_metric", "42"):
|
|
199
|
-
|
|
203
|
+
promtext_main()
|
|
200
204
|
assert promfile.exists()
|
|
201
205
|
assert promfile.read_text() == (
|
|
202
206
|
"# HELP existing_metric metric appended by promtext-cli"
|
|
@@ -209,9 +213,7 @@ def test_existing_metric_plain(tmp_path):
|
|
|
209
213
|
|
|
210
214
|
|
|
211
215
|
def test_existing_metric_labeldrop(tmp_path, capsys):
|
|
212
|
-
"""
|
|
213
|
-
missing labels are not possible, promtext should fail
|
|
214
|
-
"""
|
|
216
|
+
"""Missing labels are not possible, promtext should fail"""
|
|
215
217
|
promfile = tmp_path / "existing_file.prom"
|
|
216
218
|
promfile.write_text(
|
|
217
219
|
"# HELP existing_metric metric appended by promtext-cli"
|
|
@@ -219,14 +221,14 @@ def test_existing_metric_labeldrop(tmp_path, capsys):
|
|
|
219
221
|
"# TYPE existing_metric gauge"
|
|
220
222
|
"\n"
|
|
221
223
|
'existing_metric{testlabel="existing"} 0.0'
|
|
222
|
-
"\n"
|
|
224
|
+
"\n",
|
|
223
225
|
)
|
|
224
226
|
|
|
225
227
|
with (
|
|
226
228
|
ArgvContext("promtext", str(promfile), "existing_metric", "42"),
|
|
227
229
|
pytest.raises(SystemExit) as pytest_wrapped_e,
|
|
228
230
|
):
|
|
229
|
-
|
|
231
|
+
promtext_main()
|
|
230
232
|
assert promfile.exists()
|
|
231
233
|
assert promfile.read_text() == (
|
|
232
234
|
"# HELP existing_metric metric appended by promtext-cli"
|
|
@@ -240,16 +242,11 @@ def test_existing_metric_labeldrop(tmp_path, capsys):
|
|
|
240
242
|
assert pytest_wrapped_e.type is SystemExit
|
|
241
243
|
assert pytest_wrapped_e.value.code == 1
|
|
242
244
|
captured = capsys.readouterr()
|
|
243
|
-
assert
|
|
244
|
-
"ERROR:promtext_cli.main:previously known label 'testlabel' missing, cannot update!"
|
|
245
|
-
in captured.err
|
|
246
|
-
)
|
|
245
|
+
assert "previously known label 'testlabel' missing, cannot update!" in captured.err
|
|
247
246
|
|
|
248
247
|
|
|
249
248
|
def test_existing_metric_labelchange(tmp_path, capsys):
|
|
250
|
-
"""
|
|
251
|
-
changing labels are not possible, promtext should fail
|
|
252
|
-
"""
|
|
249
|
+
"""Changing labels are not possible, promtext should fail"""
|
|
253
250
|
promfile = tmp_path / "existing_file.prom"
|
|
254
251
|
promfile.write_text(
|
|
255
252
|
"# HELP existing_metric metric appended by promtext-cli"
|
|
@@ -257,7 +254,7 @@ def test_existing_metric_labelchange(tmp_path, capsys):
|
|
|
257
254
|
"# TYPE existing_metric gauge"
|
|
258
255
|
"\n"
|
|
259
256
|
'existing_metric{existinglabel="existing"} 0.0'
|
|
260
|
-
"\n"
|
|
257
|
+
"\n",
|
|
261
258
|
)
|
|
262
259
|
|
|
263
260
|
with (
|
|
@@ -271,7 +268,7 @@ def test_existing_metric_labelchange(tmp_path, capsys):
|
|
|
271
268
|
),
|
|
272
269
|
pytest.raises(SystemExit) as pytest_wrapped_e,
|
|
273
270
|
):
|
|
274
|
-
|
|
271
|
+
promtext_main()
|
|
275
272
|
assert promfile.exists()
|
|
276
273
|
assert promfile.read_text() == (
|
|
277
274
|
"# HELP existing_metric metric appended by promtext-cli"
|
|
@@ -286,15 +283,12 @@ def test_existing_metric_labelchange(tmp_path, capsys):
|
|
|
286
283
|
assert pytest_wrapped_e.value.code == 1
|
|
287
284
|
captured = capsys.readouterr()
|
|
288
285
|
assert (
|
|
289
|
-
"
|
|
290
|
-
in captured.err
|
|
286
|
+
"previously known label 'existinglabel' missing, cannot update!" in captured.err
|
|
291
287
|
)
|
|
292
288
|
|
|
293
289
|
|
|
294
290
|
def test_existing_metric_labeladd(tmp_path, capsys):
|
|
295
|
-
"""
|
|
296
|
-
adding new labels are not possible, promtext should fail
|
|
297
|
-
"""
|
|
291
|
+
"""Adding new labels are not possible, promtext should fail"""
|
|
298
292
|
promfile = tmp_path / "existing_file.prom"
|
|
299
293
|
promfile.write_text(
|
|
300
294
|
"# HELP existing_metric metric appended by promtext-cli"
|
|
@@ -302,7 +296,7 @@ def test_existing_metric_labeladd(tmp_path, capsys):
|
|
|
302
296
|
"# TYPE existing_metric gauge"
|
|
303
297
|
"\n"
|
|
304
298
|
'existing_metric{existinglabel="existing"} 0.0'
|
|
305
|
-
"\n"
|
|
299
|
+
"\n",
|
|
306
300
|
)
|
|
307
301
|
|
|
308
302
|
with (
|
|
@@ -318,7 +312,7 @@ def test_existing_metric_labeladd(tmp_path, capsys):
|
|
|
318
312
|
),
|
|
319
313
|
pytest.raises(SystemExit) as pytest_wrapped_e,
|
|
320
314
|
):
|
|
321
|
-
|
|
315
|
+
promtext_main()
|
|
322
316
|
assert promfile.exists()
|
|
323
317
|
assert promfile.read_text() == (
|
|
324
318
|
"# HELP existing_metric metric appended by promtext-cli"
|
|
@@ -333,7 +327,7 @@ def test_existing_metric_labeladd(tmp_path, capsys):
|
|
|
333
327
|
assert pytest_wrapped_e.value.code == 1
|
|
334
328
|
captured = capsys.readouterr()
|
|
335
329
|
assert (
|
|
336
|
-
"
|
|
330
|
+
"labelnames for metric existing_metric not the same, cannot update"
|
|
337
331
|
in captured.err
|
|
338
332
|
)
|
|
339
333
|
|
|
@@ -1,152 +0,0 @@
|
|
|
1
|
-
"""promtext_cli is providing a CLI to cleanly update prometheus textfiles from scripts"""
|
|
2
|
-
|
|
3
|
-
# pylint: disable=C0116,R0914,R0912,R0915
|
|
4
|
-
# this rules will be fixed by a object-oriented refactoring
|
|
5
|
-
|
|
6
|
-
import argparse
|
|
7
|
-
from pathlib import Path
|
|
8
|
-
import logging
|
|
9
|
-
import sys
|
|
10
|
-
|
|
11
|
-
from prometheus_client.parser import text_string_to_metric_families
|
|
12
|
-
from prometheus_client import CollectorRegistry, Gauge, write_to_textfile
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
def promtext():
|
|
16
|
-
# setup argpars
|
|
17
|
-
# required file first
|
|
18
|
-
parser = argparse.ArgumentParser(description="Prometheus textfile helper")
|
|
19
|
-
parser.add_argument(
|
|
20
|
-
"filename",
|
|
21
|
-
type=str,
|
|
22
|
-
help="Path to existing or new prometheus textfile, will be updated",
|
|
23
|
-
)
|
|
24
|
-
|
|
25
|
-
# metric name, required
|
|
26
|
-
parser.add_argument("metric", type=str, help="metric name (new or updated)")
|
|
27
|
-
|
|
28
|
-
# metric value as int/float, required
|
|
29
|
-
parser.add_argument("value", type=float, help="metric value")
|
|
30
|
-
|
|
31
|
-
# metric documentation as optional argument
|
|
32
|
-
parser.add_argument(
|
|
33
|
-
"--docs",
|
|
34
|
-
type=str,
|
|
35
|
-
help="metric documentation",
|
|
36
|
-
default="metric appended by promtext-cli",
|
|
37
|
-
)
|
|
38
|
-
|
|
39
|
-
# labels, key-value, minimum 0, repeatable
|
|
40
|
-
parser.add_argument(
|
|
41
|
-
"--label", metavar="KEY=VALUE", help="label key=value pairs", action="append"
|
|
42
|
-
)
|
|
43
|
-
|
|
44
|
-
# log level from argparse
|
|
45
|
-
parser.add_argument(
|
|
46
|
-
"-v",
|
|
47
|
-
"--verbose",
|
|
48
|
-
action="store_const",
|
|
49
|
-
dest="loglevel",
|
|
50
|
-
const=logging.INFO,
|
|
51
|
-
)
|
|
52
|
-
args = parser.parse_args()
|
|
53
|
-
logging.basicConfig(level=args.loglevel)
|
|
54
|
-
logger = logging.getLogger(__name__)
|
|
55
|
-
|
|
56
|
-
# processing: check if file is available, if yes, parse
|
|
57
|
-
textfile = Path(args.filename)
|
|
58
|
-
|
|
59
|
-
registry = CollectorRegistry()
|
|
60
|
-
metrics = {}
|
|
61
|
-
|
|
62
|
-
# check if args.filename exists with pathlib
|
|
63
|
-
if textfile.is_file():
|
|
64
|
-
for f in text_string_to_metric_families(textfile.read_text(encoding="utf-8")):
|
|
65
|
-
# per metric: iterate over samples, create metric in registry
|
|
66
|
-
m = False
|
|
67
|
-
samples = []
|
|
68
|
-
for s in f.samples:
|
|
69
|
-
samples.append(s)
|
|
70
|
-
if len(samples) > 0:
|
|
71
|
-
# if we have samples, use the labelnames from them
|
|
72
|
-
labelnames = list(samples[0].labels.keys())
|
|
73
|
-
# metric-type specific init
|
|
74
|
-
if f.type == "gauge":
|
|
75
|
-
m = Gauge(
|
|
76
|
-
f.name,
|
|
77
|
-
f.documentation,
|
|
78
|
-
unit=f.unit,
|
|
79
|
-
labelnames=labelnames,
|
|
80
|
-
registry=registry,
|
|
81
|
-
)
|
|
82
|
-
else:
|
|
83
|
-
# we don't support other types yet, continue in these cases
|
|
84
|
-
logger.warning("unsupported metric type %s, dropping", f.type)
|
|
85
|
-
continue
|
|
86
|
-
for s in samples:
|
|
87
|
-
if len(labelnames) > 0:
|
|
88
|
-
labelvalues = list(s.labels.values())
|
|
89
|
-
m.labels(*labelvalues).set(s.value)
|
|
90
|
-
else:
|
|
91
|
-
m.set(s.value)
|
|
92
|
-
metrics[f.name] = m
|
|
93
|
-
logger.info(
|
|
94
|
-
"copy gauge metric %s with labels %s from old file",
|
|
95
|
-
f.name,
|
|
96
|
-
", ".join(labelnames),
|
|
97
|
-
)
|
|
98
|
-
else:
|
|
99
|
-
logger.warning("got empty metric %s from old file, dropping", f.name)
|
|
100
|
-
|
|
101
|
-
# add new metric from commandline
|
|
102
|
-
m = False
|
|
103
|
-
|
|
104
|
-
# figure out labelkey- and values
|
|
105
|
-
labels = {}
|
|
106
|
-
if args.label:
|
|
107
|
-
for lpair in args.label:
|
|
108
|
-
k, v = lpair.split("=")
|
|
109
|
-
labels[k] = v
|
|
110
|
-
|
|
111
|
-
labelvalues = []
|
|
112
|
-
# here, we use a new metric
|
|
113
|
-
if args.metric not in metrics:
|
|
114
|
-
logger.info("adding new metric %s", args.metric)
|
|
115
|
-
m = Gauge(args.metric, args.docs, registry=registry, labelnames=labels.keys())
|
|
116
|
-
labelvalues = labels.values()
|
|
117
|
-
else:
|
|
118
|
-
m = metrics[args.metric]
|
|
119
|
-
|
|
120
|
-
# There is no way to access existing labelnames directly
|
|
121
|
-
# pylint: disable=W0212
|
|
122
|
-
old_labelnames = list(m._labelnames)
|
|
123
|
-
for la in old_labelnames:
|
|
124
|
-
logger.info("processing label %s", la)
|
|
125
|
-
if la in labels: # labelvalues are needed in order!
|
|
126
|
-
labelvalues.append(labels[la])
|
|
127
|
-
else:
|
|
128
|
-
logger.error("previously known label '%s' missing, cannot update!", la)
|
|
129
|
-
sys.exit(1)
|
|
130
|
-
if len(old_labelnames) != len(labels.keys()):
|
|
131
|
-
logger.error(
|
|
132
|
-
"labelnames for metric %s not the same, cannot update! Old: %s, New: %s",
|
|
133
|
-
args.metric,
|
|
134
|
-
old_labelnames,
|
|
135
|
-
list(labels.keys()),
|
|
136
|
-
)
|
|
137
|
-
sys.exit(1)
|
|
138
|
-
logger.info("updating metric %s", args.metric)
|
|
139
|
-
|
|
140
|
-
# actually set the value
|
|
141
|
-
if len(labelvalues) > 0:
|
|
142
|
-
m.labels(*labelvalues).set(args.value)
|
|
143
|
-
else:
|
|
144
|
-
m.set(args.value)
|
|
145
|
-
|
|
146
|
-
# write to file
|
|
147
|
-
write_to_textfile(args.filename, registry)
|
|
148
|
-
logger.info("wrote to %s", args.filename)
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
if __name__ == "__main__":
|
|
152
|
-
promtext()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|