promtext-cli 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.
@@ -0,0 +1,14 @@
1
+ ---
2
+ name: Lint
3
+ on:
4
+ push:
5
+ pull_request:
6
+
7
+ jobs:
8
+ lint:
9
+ runs-on: ubuntu-latest
10
+ steps:
11
+ - uses: actions/checkout@v3
12
+ - run: pipx install hatch
13
+ - run: hatch run ci:check
14
+ - run: hatch run ci:lint
@@ -0,0 +1,87 @@
1
+ name: Publish Python 🐍 distribution 📦 to PyPI and TestPyPI
2
+ # based on https://packaging.python.org/en/latest/guides/publishing-package-distribution-releases-using-github-actions-ci-cd-workflows
3
+
4
+ on: push
5
+
6
+ jobs:
7
+ build:
8
+ name: Build distribution 📦
9
+ runs-on: ubuntu-latest
10
+
11
+ steps:
12
+ - uses: actions/checkout@v4
13
+ with:
14
+ fetch-depth: 0 # fetch all history for tags, allowing to build proper dev version
15
+ - run: pipx install hatch
16
+ - run: hatch build
17
+ - name: Store the distribution packages
18
+ uses: actions/upload-artifact@v3
19
+ with:
20
+ name: python-package-distributions
21
+ path: dist/
22
+
23
+ publish-to-pypi:
24
+ name: >-
25
+ Publish Python 🐍 distribution 📦 to PyPI
26
+ if: startsWith(github.ref, 'refs/tags/') || github.ref == 'refs/heads/main' # only publish to PyPI on tag pushes or dev version for main
27
+ needs:
28
+ - build
29
+ runs-on: ubuntu-latest
30
+ environment:
31
+ name: pypi
32
+ url: https://pypi.org/p/promtext-cli
33
+ permissions:
34
+ id-token: write # IMPORTANT: mandatory for trusted publishing
35
+
36
+ steps:
37
+ - name: Download all the dists
38
+ uses: actions/download-artifact@v3
39
+ with:
40
+ name: python-package-distributions
41
+ path: dist/
42
+ - name: Publish distribution 📦 to PyPI
43
+ uses: pypa/gh-action-pypi-publish@release/v1
44
+
45
+ github-release:
46
+ name: >-
47
+ Sign the Python 🐍 distribution 📦 with Sigstore
48
+ and upload them to GitHub Release
49
+ if: startsWith(github.ref, 'refs/tags/') # only publish to PyPI on tag pushes
50
+ needs:
51
+ - publish-to-pypi
52
+ runs-on: ubuntu-latest
53
+
54
+ permissions:
55
+ contents: write # IMPORTANT: mandatory for making GitHub Releases
56
+ id-token: write # IMPORTANT: mandatory for sigstore
57
+
58
+ steps:
59
+ - name: Download all the dists
60
+ uses: actions/download-artifact@v3
61
+ with:
62
+ name: python-package-distributions
63
+ path: dist/
64
+ - name: Sign the dists with Sigstore
65
+ uses: sigstore/gh-action-sigstore-python@v1.2.3
66
+ with:
67
+ inputs: >-
68
+ ./dist/*.tar.gz
69
+ ./dist/*.whl
70
+ - name: Create GitHub Release
71
+ env:
72
+ GITHUB_TOKEN: ${{ github.token }}
73
+ run: >-
74
+ gh release create
75
+ '${{ github.ref_name }}'
76
+ --repo '${{ github.repository }}'
77
+ --notes ""
78
+ - name: Upload artifact signatures to GitHub Release
79
+ env:
80
+ GITHUB_TOKEN: ${{ github.token }}
81
+ # Upload to GitHub Release using the `gh` CLI.
82
+ # `dist/` contains the built packages, and the
83
+ # sigstore-produced signatures and certificates.
84
+ run: >-
85
+ gh release upload
86
+ '${{ github.ref_name }}' dist/**
87
+ --repo '${{ github.repository }}'
@@ -0,0 +1,3 @@
1
+ tmp/
2
+ /dist
3
+ __pycache__
@@ -0,0 +1,106 @@
1
+ Metadata-Version: 2.3
2
+ Name: promtext-cli
3
+ Version: 0.1.0
4
+ Summary: Prometheus Textfile Tooling
5
+ Project-URL: Issues, https://github.com/margau/promtext-cli/issues
6
+ Project-URL: Source, https://github.com/margau/promtext-cli
7
+ Author-email: margau <dev@marvingaube.de>
8
+ License-Expression: GPL-3.0
9
+ Keywords: Prometheus
10
+ Requires-Python: >=3.7
11
+ Requires-Dist: prometheus-client
12
+ Description-Content-Type: text/markdown
13
+
14
+ # promtext-cli
15
+
16
+ promtext-cli is a tool for creating prometheus text files from a simple cli command.
17
+
18
+ It is intended for use with cronjob scripts (e.g. backups).
19
+
20
+ Features:
21
+ - supports merging new metrics into existing files
22
+ - metrics will be updated (same labelset or no labels given), or appended to existing metrics as new timeseries
23
+ - currently only supports gauge metrics
24
+
25
+ ## Usage
26
+ ```
27
+ promtext -h
28
+ usage: main.py [-h] [--docs DOCS] [--label KEY=VALUE] [-v] filename metric value
29
+
30
+ Prometheus textfile helper
31
+
32
+ positional arguments:
33
+ filename Path to existing or new prometheus textfile, will be updated
34
+ metric metric name (new or updated)
35
+ value metric value
36
+
37
+ options:
38
+ -h, --help show this help message and exit
39
+ --docs DOCS metric documentation
40
+ --label KEY=VALUE label key=value pairs
41
+ -v, --verbose
42
+ ```
43
+
44
+ ## Examples
45
+ `tmp/backup.prom` before:
46
+ ```
47
+ # HELP backup_last_start
48
+ # TYPE backup_last_start gauge
49
+ backup_last_start{backup="example_1"} 1.721923501e+09
50
+ # HELP backup_last_end
51
+ # TYPE backup_last_end gauge
52
+ backup_last_end{backup="example_1"} 1.721989156e+09
53
+ # HELP backup_last_exit
54
+ # TYPE backup_last_exit gauge
55
+ backup_last_exit{backup="example_1"} 2.0
56
+ ```
57
+
58
+ Updating existing timeseries: `promtext tmp/backup.prom backup_last_start 0 --label backup=example_1`:
59
+ ```
60
+ # HELP backup_last_start
61
+ # TYPE backup_last_start gauge
62
+ backup_last_start{backup="example_1"} 0.0
63
+ # HELP backup_last_end
64
+ # TYPE backup_last_end gauge
65
+ backup_last_end{backup="example_1"} 1.721989156e+09
66
+ # HELP backup_last_exit
67
+ # TYPE backup_last_exit gauge
68
+ backup_last_exit{backup="example_1"} 2.0
69
+ ```
70
+
71
+ Adding a new label: `promtext tmp/backup.prom backup_last_start 0 --label backup=example_2`
72
+ ```
73
+ # HELP backup_last_start
74
+ # TYPE backup_last_start gauge
75
+ backup_last_start{backup="example_1"} 0.0
76
+ backup_last_start{backup="example_2"} 0.0
77
+ # HELP backup_last_end
78
+ # TYPE backup_last_end gauge
79
+ backup_last_end{backup="example_1"} 1.721989156e+09
80
+ # HELP backup_last_exit
81
+ # TYPE backup_last_exit gauge
82
+ backup_last_exit{backup="example_1"} 2.0
83
+ ```
84
+
85
+ Adding a new metric: `promtext tmp/backup.prom some_other_state 0 --label new_label=foo_bar`
86
+ ```
87
+ # HELP backup_last_start
88
+ # TYPE backup_last_start gauge
89
+ backup_last_start{backup="example_1"} 0.0
90
+ backup_last_start{backup="example_2"} 0.0
91
+ # HELP backup_last_end
92
+ # TYPE backup_last_end gauge
93
+ backup_last_end{backup="example_1"} 1.721989156e+09
94
+ # HELP backup_last_exit
95
+ # TYPE backup_last_exit gauge
96
+ backup_last_exit{backup="example_1"} 2.0
97
+ # HELP some_other_state metric appended by promtext-cli
98
+ # TYPE some_other_state gauge
99
+ some_other_state{new_label="foo_bar"} 0.0
100
+ ```
101
+
102
+ However, changing the label keys does not work:
103
+ ```
104
+ promtext tmp/backup.prom some_other_state 0 --label foo_bar=foo_bar
105
+ ERROR:promtext_cli.main:labelnames for metric some_other_state not compatible, cannot update! Old: ['new_label'], New: ['foo_bar']
106
+ ```
@@ -0,0 +1,93 @@
1
+ # promtext-cli
2
+
3
+ promtext-cli is a tool for creating prometheus text files from a simple cli command.
4
+
5
+ It is intended for use with cronjob scripts (e.g. backups).
6
+
7
+ Features:
8
+ - supports merging new metrics into existing files
9
+ - metrics will be updated (same labelset or no labels given), or appended to existing metrics as new timeseries
10
+ - currently only supports gauge metrics
11
+
12
+ ## Usage
13
+ ```
14
+ promtext -h
15
+ usage: main.py [-h] [--docs DOCS] [--label KEY=VALUE] [-v] filename metric value
16
+
17
+ Prometheus textfile helper
18
+
19
+ positional arguments:
20
+ filename Path to existing or new prometheus textfile, will be updated
21
+ metric metric name (new or updated)
22
+ value metric value
23
+
24
+ options:
25
+ -h, --help show this help message and exit
26
+ --docs DOCS metric documentation
27
+ --label KEY=VALUE label key=value pairs
28
+ -v, --verbose
29
+ ```
30
+
31
+ ## Examples
32
+ `tmp/backup.prom` before:
33
+ ```
34
+ # HELP backup_last_start
35
+ # TYPE backup_last_start gauge
36
+ backup_last_start{backup="example_1"} 1.721923501e+09
37
+ # HELP backup_last_end
38
+ # TYPE backup_last_end gauge
39
+ backup_last_end{backup="example_1"} 1.721989156e+09
40
+ # HELP backup_last_exit
41
+ # TYPE backup_last_exit gauge
42
+ backup_last_exit{backup="example_1"} 2.0
43
+ ```
44
+
45
+ Updating existing timeseries: `promtext tmp/backup.prom backup_last_start 0 --label backup=example_1`:
46
+ ```
47
+ # HELP backup_last_start
48
+ # TYPE backup_last_start gauge
49
+ backup_last_start{backup="example_1"} 0.0
50
+ # HELP backup_last_end
51
+ # TYPE backup_last_end gauge
52
+ backup_last_end{backup="example_1"} 1.721989156e+09
53
+ # HELP backup_last_exit
54
+ # TYPE backup_last_exit gauge
55
+ backup_last_exit{backup="example_1"} 2.0
56
+ ```
57
+
58
+ Adding a new label: `promtext tmp/backup.prom backup_last_start 0 --label backup=example_2`
59
+ ```
60
+ # HELP backup_last_start
61
+ # TYPE backup_last_start gauge
62
+ backup_last_start{backup="example_1"} 0.0
63
+ backup_last_start{backup="example_2"} 0.0
64
+ # HELP backup_last_end
65
+ # TYPE backup_last_end gauge
66
+ backup_last_end{backup="example_1"} 1.721989156e+09
67
+ # HELP backup_last_exit
68
+ # TYPE backup_last_exit gauge
69
+ backup_last_exit{backup="example_1"} 2.0
70
+ ```
71
+
72
+ Adding a new metric: `promtext tmp/backup.prom some_other_state 0 --label new_label=foo_bar`
73
+ ```
74
+ # HELP backup_last_start
75
+ # TYPE backup_last_start gauge
76
+ backup_last_start{backup="example_1"} 0.0
77
+ backup_last_start{backup="example_2"} 0.0
78
+ # HELP backup_last_end
79
+ # TYPE backup_last_end gauge
80
+ backup_last_end{backup="example_1"} 1.721989156e+09
81
+ # HELP backup_last_exit
82
+ # TYPE backup_last_exit gauge
83
+ backup_last_exit{backup="example_1"} 2.0
84
+ # HELP some_other_state metric appended by promtext-cli
85
+ # TYPE some_other_state gauge
86
+ some_other_state{new_label="foo_bar"} 0.0
87
+ ```
88
+
89
+ However, changing the label keys does not work:
90
+ ```
91
+ promtext tmp/backup.prom some_other_state 0 --label foo_bar=foo_bar
92
+ ERROR:promtext_cli.main:labelnames for metric some_other_state not compatible, cannot update! Old: ['new_label'], New: ['foo_bar']
93
+ ```
File without changes
@@ -0,0 +1,120 @@
1
+ """promtext_cli is providing a CLI to cleanly update prometheus textfiles from scripts"""
2
+
3
+ import argparse
4
+ from pathlib import Path
5
+ import logging
6
+ import sys
7
+
8
+ from prometheus_client.parser import text_string_to_metric_families
9
+ from prometheus_client import CollectorRegistry, Gauge, write_to_textfile
10
+
11
+ def promtext():
12
+ # setup argpars
13
+ # required file first
14
+ parser = argparse.ArgumentParser(description='Prometheus textfile helper')
15
+ parser.add_argument(
16
+ 'filename',
17
+ type=str,
18
+ help='Path to existing or new prometheus textfile, will be updated'
19
+ )
20
+
21
+ # metric name, required
22
+ parser.add_argument(
23
+ 'metric',
24
+ type=str,
25
+ help='metric name (new or updated)')
26
+
27
+ # metric value as int/float, required
28
+ parser.add_argument('value', type=float, help='metric value')
29
+
30
+ # metric documentation as optional argument
31
+ parser.add_argument(
32
+ '--docs', type=str,
33
+ help='metric documentation', default="metric appended by promtext-cli")
34
+
35
+ # labels, key-value, minimum 0, repeatable
36
+ parser.add_argument("--label", metavar="KEY=VALUE", help='label key=value pairs', action='append')
37
+
38
+ # log level from argparse
39
+ parser.add_argument(
40
+ '-v', '--verbose',
41
+ action="store_const", dest="loglevel", const=logging.INFO,
42
+ )
43
+ args = parser.parse_args()
44
+ logging.basicConfig(level=args.loglevel)
45
+ logger = logging.getLogger(__name__)
46
+
47
+ # processing: check if file is available, if yes, parse
48
+ textfile = Path(args.filename)
49
+
50
+ registry = CollectorRegistry()
51
+ metrics = {}
52
+
53
+ # check if args.filename exists with pathlib
54
+ if textfile.is_file():
55
+ for f in text_string_to_metric_families(textfile.read_text()):
56
+ # per metric: iterate over samples, create metric in registry
57
+ m = False
58
+ samples = []
59
+ for s in f.samples:
60
+ samples.append(s)
61
+ if len(samples) > 0:
62
+ # if we have samples, use the labelnames from them
63
+ labelnames = list(samples[0].labels.keys())
64
+ # metric-type specific init
65
+ if f.type == "gauge":
66
+ m = Gauge(f.name, f.documentation,
67
+ unit=f.unit, labelnames=labelnames, registry=registry)
68
+ else:
69
+ # we don't support other types yet, continue in these cases
70
+ logger.warning("unsupported metric type %s, dropping", f.type)
71
+ continue
72
+ for s in samples:
73
+ if len(labelnames) > 0:
74
+ labelvalues = list(s.labels.values())
75
+ m.labels(*labelvalues).set(s.value)
76
+ else:
77
+ m.set(s.value)
78
+ metrics[f.name] = m
79
+ logger.info("copy gauge metric %s with labels %s from old file",
80
+ f.name, ', '.join(labelnames))
81
+ else:
82
+ logger.warning("got empty metric %s from old file, dropping", f.name)
83
+
84
+ # add new metric from commandline
85
+ m = False
86
+
87
+ # figure out labelkey- and values
88
+ labelnames = []
89
+ labelvalues = []
90
+ if args.label:
91
+ for lpair in args.label:
92
+ k, v = lpair.split("=")
93
+ labelnames.append(k)
94
+ labelvalues.append(v)
95
+
96
+ # here, we use a new metric
97
+ if args.metric not in metrics:
98
+ logger.info("adding new metric %s", args.metric)
99
+ m = Gauge(args.metric, args.docs, registry=registry, labelnames=labelnames)
100
+ else:
101
+ m = metrics[args.metric]
102
+ old_labelnames = list(m._labelnames)
103
+ if old_labelnames != labelnames:
104
+ logger.error("labelnames for metric %s not compatible, cannot update! Old: %s, New: %s",
105
+ args.metric, old_labelnames, labelnames)
106
+ sys.exit(1)
107
+ logger.info("updating metric %s", args.metric)
108
+
109
+ # actually set the value
110
+ if len(labelnames) > 0:
111
+ m.labels(*labelvalues).set(args.value)
112
+ else:
113
+ m.set(args.value)
114
+
115
+ # write to file
116
+ write_to_textfile(args.filename, registry)
117
+ logger.info("wrote to %s", args.filename)
118
+
119
+ if __name__ == '__main__':
120
+ promtext()
@@ -0,0 +1,41 @@
1
+ [build-system]
2
+ requires = ["hatchling", "hatch-vcs"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "promtext-cli"
7
+ description = 'Prometheus Textfile Tooling'
8
+ readme = "README.md"
9
+ requires-python = ">=3.7"
10
+ license = "GPL-3.0"
11
+ keywords = [
12
+ "Prometheus"
13
+ ]
14
+ authors = [
15
+ { name = "margau", email = "dev@marvingaube.de" }
16
+ ]
17
+ classifiers = []
18
+ dependencies = [
19
+ "prometheus_client"
20
+ ]
21
+ dynamic = ["version"]
22
+
23
+ [project.urls]
24
+ Issues = "https://github.com/margau/promtext-cli/issues"
25
+ Source = "https://github.com/margau/promtext-cli"
26
+
27
+ [project.scripts]
28
+ promtext = "promtext_cli.main:promtext"
29
+
30
+ [tool.hatch.version]
31
+ source = "vcs"
32
+
33
+ [tool.hatch.envs.ci]
34
+ dependencies = [
35
+ "pylint",
36
+ "ruff",
37
+ ]
38
+
39
+ [tool.hatch.envs.ci.scripts]
40
+ check = "ruff check promtext_cli"
41
+ lint = "pylint promtext_cli || exit 0"