From 321afe1a22a4a5ed962606c2f5b949807397d89a Mon Sep 17 00:00:00 2001 From: Miguel Garcia Date: Thu, 27 Jan 2022 15:49:41 +0100 Subject: [PATCH] Enable pip installs and Cachito compatibility (#9) * Add setup.py for pip compatibility This allows prometheus_webhook_snmp to be installed using pip install. * Add requirements and build requirements, all with locked versions Cachito requires locking all versions to work. It is also generally a good thing to do. * Dockerfile: install with pip instead of RPMs --- Dockerfile | 36 ++++- MANIFEST.in | 10 ++ prometheus-webhook-snmp | 122 +---------------- .../prometheus_webhook_snmp.py | 124 ++++++++++++++++++ requirements-build.in | 18 +++ requirements-build.txt | 26 ++++ requirements.in | 11 ++ requirements.txt | 88 ++++++++++++- setup.py | 59 +++++++++ 9 files changed, 364 insertions(+), 130 deletions(-) create mode 100644 MANIFEST.in create mode 100755 prometheus_webhook_snmp/prometheus_webhook_snmp.py create mode 100644 requirements-build.in create mode 100644 requirements-build.txt create mode 100644 requirements.in create mode 100644 setup.py diff --git a/Dockerfile b/Dockerfile index 5b4afdb..3ae2952 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,10 +1,32 @@ -FROM quay.io/centos/centos:centos8 +FROM quay.io/centos/centos:stream8 -RUN dnf -y install centos-release-opstools && \ - dnf -y install python3-cherrypy python3-PyYAML python3-pysnmp \ - python3-dateutil python3-click git \ - prometheus-webhook-snmp \ - python3-prometheus_client procps-ng lsof && dnf clean all +RUN INSTALL_PKGS="\ + procps-ng \ + telnet \ + lsof \ + python3 \ + python3-devel \ + gcc \ + " && \ + dnf -y --setopt=tsflags=nodocs --setopt=skip_missing_names_on_install=False install $INSTALL_PKGS && \ + dnf -y clean all + +COPY . /source/app +WORKDIR /source/app + +RUN alternatives --set python /usr/bin/python3 && \ + python -m pip install --upgrade setuptools pip && \ + python -m pip install wheel && \ + python -m pip install -r requirements-build.txt && \ + python -m pip install . && \ + python -m pip freeze + +# Cleanup +RUN UNINSTALL_PKGS="\ + gcc \ + " && \ + dnf remove -y $UNINSTALL_PKGS && \ + dnf -y clean all ENV SNMP_COMMUNITY="public" ENV SNMP_PORT=162 @@ -15,4 +37,4 @@ ENV ALERT_OID_LABEL="oid" EXPOSE 9099 -CMD ["sh", "-c", "/usr/bin/prometheus-webhook-snmp --debug --snmp-port=$SNMP_PORT --snmp-host=$SNMP_HOST --snmp-community=$SNMP_COMMUNITY --alert-oid-label=$ALERT_OID_LABEL run"] +CMD ["sh", "-c", "/usr/local/bin/prometheus-webhook-snmp --debug --snmp-port=$SNMP_PORT --snmp-host=$SNMP_HOST --snmp-community=$SNMP_COMMUNITY --alert-oid-label=$ALERT_OID_LABEL run"] diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..8321977 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,10 @@ +include CHANGELOG.md +include LICENSE +include README.md + +recursive-exclude tests * +recursive-exclude tmp * +recursive-exclude debian * +recursive-exclude .github * +recursive-exclude * __pycache__ +recursive-exclude * *.py[co] diff --git a/prometheus-webhook-snmp b/prometheus-webhook-snmp index 2f15918..1065121 100755 --- a/prometheus-webhook-snmp +++ b/prometheus-webhook-snmp @@ -1,124 +1,8 @@ -#!/usr/bin/python3 -import datetime -import logging +#!/usr/bin/env python import sys -import click - -from prometheus_webhook_snmp import utils - - -__version__ = "1.5" - - -pass_context = click.make_pass_decorator(utils.Context, ensure=True) - - -@click.group(help='Prometheus Alertmanager receiver that translates ' - 'notifications into SNMP v2c traps.') -@click.option('--debug', - is_flag=True, - help='Enable debug output.') -@click.option('--snmp-host', - help='The host (IPv4, IPv6 or FQDN) of the SNMP trap receiver.') -@click.option('--snmp-port', - help='The port of the SNMP trap receiver.', - type=int) -@click.option('--snmp-community', - help='The SNMP community string.') -@click.option('--snmp-retries', - help='Maximum number of request retries.', - type=int) -@click.option('--snmp-timeout', - help='Response timeout in seconds.', - type=int) -@click.option('--alert-oid-label', - help='The label where to find the OID.') -@click.option('--trap-oid-prefix', - help='The OID prefix for trap variable bindings.') -@click.option('--trap-default-oid', - help='The trap OID if none is found in the Prometheus alert labels.') -@click.option('--trap-default-severity', - help='The trap severity if none is found in the Prometheus alert labels.') -@click.version_option(__version__, message="%(version)s") -@pass_context -def cli(ctx, debug, snmp_host, snmp_port, snmp_community, snmp_retries, - snmp_timeout, alert_oid_label, trap_oid_prefix, trap_default_oid, - trap_default_severity): - ctx.config.load(click.get_current_context().info_name) - ctx.config['debug'] = True if debug else None - ctx.config['snmp_host'] = snmp_host - ctx.config['snmp_port'] = snmp_port - ctx.config['snmp_community'] = snmp_community - ctx.config['snmp_retries'] = snmp_retries - ctx.config['snmp_timeout'] = snmp_timeout - ctx.config['alert_oid_label'] = alert_oid_label - ctx.config['trap_default_oid'] = trap_default_oid - ctx.config['trap_default_severity'] = trap_default_severity - ctx.config['trap_oid_prefix'] = trap_oid_prefix - - if ctx.config['debug']: - logging.basicConfig(level=logging.DEBUG) - - return 0 - - -@cli.command(name='run', help='Start the HTTP server.') -@click.option('--host', - help='Host to use.') -@click.option('--port', - help='Port to listen for Prometheus Alertmanager notifications.', - type=int) -@click.option('--metrics', - is_flag=True, - help='Provide Prometheus metrics from this receiver.') -@pass_context -def run(ctx, host, port, metrics): - ctx.config['host'] = host - ctx.config['port'] = port - ctx.config['metrics'] = True if metrics else None - ctx.config.dump() - utils.run_http_server(ctx) - sys.exit(0) - - -@cli.command(name='test', help='Trigger a SNMP trap.') -@pass_context -def test(ctx): - ctx.config.dump() - trap_data = utils.parse_notification(ctx.config, { - 'receiver': 'bar', - 'status': 'firing', - 'alerts': [{ - 'status': 'firing', - 'labels': { - 'alertname': 'load_0', - 'instance': 'localhost:9100', - 'job': 'node-exporter', - 'severity': 'info', - 'foo': 'bar', - 'xyz': 'abc' - }, - 'annotations': { - 'description': 'localhost:9100 of job node-exporter load is over 0!', - 'summary': 'Instance localhost:9100 load is over 0!' - }, - 'startsAt': datetime.datetime.utcnow().isoformat() + 'Z', - 'endsAt': '0001-01-01T00:00:00Z', - 'generatorURL': 'http://foo:9090/graph?...' - }], - 'groupLabels': {}, - 'commonLabels': {}, - 'commonAnnotations': {}, - 'externalURL': '', - 'version': '4', - 'groupKey': '{}:{}' - }) - if trap_data is not None: - for data in trap_data: - utils.send_snmp_trap(ctx.config, data) - sys.exit(0) +from prometheus_webhook_snmp import prometheus_webhook_snmp if __name__ == "__main__": - sys.exit(cli()) # pylint: disable=no-value-for-parameter + sys.exit(prometheus_webhook_snmp.cli()) # pylint: disable=no-value-for-parameter diff --git a/prometheus_webhook_snmp/prometheus_webhook_snmp.py b/prometheus_webhook_snmp/prometheus_webhook_snmp.py new file mode 100755 index 0000000..2f15918 --- /dev/null +++ b/prometheus_webhook_snmp/prometheus_webhook_snmp.py @@ -0,0 +1,124 @@ +#!/usr/bin/python3 +import datetime +import logging +import sys + +import click + +from prometheus_webhook_snmp import utils + + +__version__ = "1.5" + + +pass_context = click.make_pass_decorator(utils.Context, ensure=True) + + +@click.group(help='Prometheus Alertmanager receiver that translates ' + 'notifications into SNMP v2c traps.') +@click.option('--debug', + is_flag=True, + help='Enable debug output.') +@click.option('--snmp-host', + help='The host (IPv4, IPv6 or FQDN) of the SNMP trap receiver.') +@click.option('--snmp-port', + help='The port of the SNMP trap receiver.', + type=int) +@click.option('--snmp-community', + help='The SNMP community string.') +@click.option('--snmp-retries', + help='Maximum number of request retries.', + type=int) +@click.option('--snmp-timeout', + help='Response timeout in seconds.', + type=int) +@click.option('--alert-oid-label', + help='The label where to find the OID.') +@click.option('--trap-oid-prefix', + help='The OID prefix for trap variable bindings.') +@click.option('--trap-default-oid', + help='The trap OID if none is found in the Prometheus alert labels.') +@click.option('--trap-default-severity', + help='The trap severity if none is found in the Prometheus alert labels.') +@click.version_option(__version__, message="%(version)s") +@pass_context +def cli(ctx, debug, snmp_host, snmp_port, snmp_community, snmp_retries, + snmp_timeout, alert_oid_label, trap_oid_prefix, trap_default_oid, + trap_default_severity): + ctx.config.load(click.get_current_context().info_name) + ctx.config['debug'] = True if debug else None + ctx.config['snmp_host'] = snmp_host + ctx.config['snmp_port'] = snmp_port + ctx.config['snmp_community'] = snmp_community + ctx.config['snmp_retries'] = snmp_retries + ctx.config['snmp_timeout'] = snmp_timeout + ctx.config['alert_oid_label'] = alert_oid_label + ctx.config['trap_default_oid'] = trap_default_oid + ctx.config['trap_default_severity'] = trap_default_severity + ctx.config['trap_oid_prefix'] = trap_oid_prefix + + if ctx.config['debug']: + logging.basicConfig(level=logging.DEBUG) + + return 0 + + +@cli.command(name='run', help='Start the HTTP server.') +@click.option('--host', + help='Host to use.') +@click.option('--port', + help='Port to listen for Prometheus Alertmanager notifications.', + type=int) +@click.option('--metrics', + is_flag=True, + help='Provide Prometheus metrics from this receiver.') +@pass_context +def run(ctx, host, port, metrics): + ctx.config['host'] = host + ctx.config['port'] = port + ctx.config['metrics'] = True if metrics else None + ctx.config.dump() + utils.run_http_server(ctx) + sys.exit(0) + + +@cli.command(name='test', help='Trigger a SNMP trap.') +@pass_context +def test(ctx): + ctx.config.dump() + trap_data = utils.parse_notification(ctx.config, { + 'receiver': 'bar', + 'status': 'firing', + 'alerts': [{ + 'status': 'firing', + 'labels': { + 'alertname': 'load_0', + 'instance': 'localhost:9100', + 'job': 'node-exporter', + 'severity': 'info', + 'foo': 'bar', + 'xyz': 'abc' + }, + 'annotations': { + 'description': 'localhost:9100 of job node-exporter load is over 0!', + 'summary': 'Instance localhost:9100 load is over 0!' + }, + 'startsAt': datetime.datetime.utcnow().isoformat() + 'Z', + 'endsAt': '0001-01-01T00:00:00Z', + 'generatorURL': 'http://foo:9090/graph?...' + }], + 'groupLabels': {}, + 'commonLabels': {}, + 'commonAnnotations': {}, + 'externalURL': '', + 'version': '4', + 'groupKey': '{}:{}' + }) + if trap_data is not None: + for data in trap_data: + utils.send_snmp_trap(ctx.config, data) + sys.exit(0) + + +if __name__ == "__main__": + sys.exit(cli()) # pylint: disable=no-value-for-parameter diff --git a/requirements-build.in b/requirements-build.in new file mode 100644 index 0000000..b61ea4a --- /dev/null +++ b/requirements-build.in @@ -0,0 +1,18 @@ +# Generated by pip_find_builddeps.py on May 19 2021 12:57:26 +Cython +setuptools +setuptools-scm +setuptools>=30.3.0 +setuptools>=34.4 +setuptools>=40.0 +setuptools>=40.6.0 +setuptools>=40.8.0 +setuptools>=42 +setuptools>=45 +setuptools_scm +setuptools_scm<6.0,>=3.3.1 +setuptools_scm>=1.15 +setuptools_scm[toml]>=3.4.1 +setuptools_scm_git_archive>=1.0 +toml +wheel diff --git a/requirements-build.txt b/requirements-build.txt new file mode 100644 index 0000000..cf193a9 --- /dev/null +++ b/requirements-build.txt @@ -0,0 +1,26 @@ +# +# This file is autogenerated by pip-compile +# To update, run: +# +# pip-compile --allow-unsafe --output-file=requirements-build.txt requirements-build.in +# +pip==21.1.1 + # for Cachito debugging +cython==0.29.23 + # via -r requirements-build.in +setuptools-scm-git-archive==1.1 + # via -r requirements-build.in +setuptools-scm[toml]==5.0.2 + # via -r requirements-build.in +toml==0.10.2 + # via + # -r requirements-build.in + # setuptools-scm +wheel==0.36.2 + # via -r requirements-build.in + +# The following packages are considered to be unsafe in a requirements file: +setuptools==56.2.0 + # via + # -r requirements-build.in + # setuptools-scm diff --git a/requirements.in b/requirements.in new file mode 100644 index 0000000..4fd6e41 --- /dev/null +++ b/requirements.in @@ -0,0 +1,11 @@ +CherryPy==18.1.1 +Click==7.0 +PyYAML==5.4 +mock==3.0.5 +prometheus_client==0.6.0 +pyfakefs==3.5.8 +pylint==2.3.1 +pysnmp==4.4.8 +pytest==4.4.0 +pytest-runner==5.3.1 +python-dateutil==2.8.0 diff --git a/requirements.txt b/requirements.txt index b93958a..494a12e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,10 +1,90 @@ -CherryPy==18.1.1 -Click==7.0 -PyYAML==5.4 +# +# This file is autogenerated by pip-compile +# To update, run: +# +# pip-compile --output-file=requirements.txt requirements.in +# +astroid==2.5.6 + # via pylint +atomicwrites==1.4.0 + # via pytest +attrs==21.2.0 + # via pytest +cheroot==8.5.2 + # via cherrypy +cherrypy==18.1.1 + # via -r requirements.in +click==7.0 + # via -r requirements.in +importlib-metadata==4.0.1 + # via pluggy +isort==4.3.21 + # via pylint +jaraco.functools==3.3.0 + # via + # cheroot + # tempora +lazy-object-proxy==1.6.0 + # via astroid +mccabe==0.6.1 + # via pylint mock==3.0.5 + # via -r requirements.in +more-itertools==8.7.0 + # via + # cheroot + # cherrypy + # jaraco.functools + # pytest +pluggy==0.13.1 + # via pytest +ply==3.11 + # via pysmi +portend==2.7.1 + # via cherrypy prometheus_client==0.6.0 + # via -r requirements.in +py==1.10.0 + # via pytest +pyasn1==0.4.8 + # via pysnmp +pycryptodomex==3.10.1 + # via pysnmp pyfakefs==3.5.8 + # via -r requirements.in pylint==2.3.1 -pysnmp>=4.4.1 + # via -r requirements.in +pysmi==0.3.4 + # via pysnmp +pysnmp==4.4.8 + # via -r requirements.in +pytest-runner==5.3.1 + # via -r requirements.in pytest==4.4.0 + # via -r requirements.in python-dateutil==2.8.0 + # via -r requirements.in +pytz==2021.1 + # via tempora +pyyaml==5.4 + # via -r requirements.in +six==1.16.0 + # via + # cheroot + # mock + # pytest + # python-dateutil +tempora==4.0.2 + # via portend +typed-ast==1.4.3 + # via astroid +typing-extensions==3.10.0.0 + # via importlib-metadata +wrapt==1.12.1 + # via astroid +zc.lockfile==2.0 + # via cherrypy +zipp==3.4.1 + # via importlib-metadata +# The following packages are considered to be unsafe in a requirements file: +# setuptools diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..eba8e08 --- /dev/null +++ b/setup.py @@ -0,0 +1,59 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import setuptools + + +with open('README.md') as readme_file: + readme = readme_file.read() + +with open('CHANGELOG.md') as changelog_file: + changelog = changelog_file.read() + + +requirements = [ + 'CherryPy==18.1.1', + 'Click==7.0', + 'PyYAML==5.4', + 'mock==3.0.5', + 'prometheus_client==0.6.0', + 'pyfakefs==3.5.8', + 'pylint==2.3.1', + 'pysnmp==4.4.8', + 'pytest==4.4.0', + 'python-dateutil==2.8.0', + 'pytest-runner==5.3.1', +] + +dependency_links = [ +] + +test_requirements = [ + # TODO: put package test requirements here +] + +setuptools.setup( + name='prometheus-webhook-snmp', + version='1.5', + description=("Prometheus Alertmanager receiver for SNMP traps"), + long_description=readme + '\n---\n' + changelog, + long_description_content_type='text/markdown', + url='https://github.com/infrawatch/prometheus-webhook-snmp', + py_modules=['prometheus_webhook_snmp.prometheus_webhook_snmp', 'prometheus_webhook_snmp.utils'], + include_package_data=True, + dependency_links=dependency_links, + install_requires=requirements, + license="GPLv3", + data_files=[ + ('./share/doc/prometheus-webhook-snmp', ['CHANGELOG.md', 'README.md']), + ], + zip_safe=True, + keywords='prometheus_webhook_snmp', + test_suite='tests', + tests_require=test_requirements, + entry_points={ + 'console_scripts': [ + 'prometheus-webhook-snmp=prometheus_webhook_snmp.prometheus_webhook_snmp:cli', + ], + } +)