From 317978c24a5e2c6447ff2fff888ac77bc68688ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lauri=20V=C3=B5sandi?= Date: Sun, 13 Jun 2021 13:58:39 +0300 Subject: [PATCH] Initial commit --- .flake8 | 6 +++++ .gitignore | 1 + .gitlint | 9 +++++++ .pre-commit-config.yaml | 16 +++++++++++ Dockerfile | 4 +++ docker-compose.yml | 8 ++++++ wildduck_exporter.py | 60 +++++++++++++++++++++++++++++++++++++++++ 7 files changed, 104 insertions(+) create mode 100644 .flake8 create mode 100644 .gitignore create mode 100644 .gitlint create mode 100644 .pre-commit-config.yaml create mode 100644 Dockerfile create mode 100644 docker-compose.yml create mode 100755 wildduck_exporter.py diff --git a/.flake8 b/.flake8 new file mode 100644 index 0000000..4258b64 --- /dev/null +++ b/.flake8 @@ -0,0 +1,6 @@ +[flake8] +inline-quotes = " +multiline-quotes = """ +indent-size = 4 +max-line-length = 160 +ignore = Q003 E128 E704 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4c49bd7 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.env diff --git a/.gitlint b/.gitlint new file mode 100644 index 0000000..9045422 --- /dev/null +++ b/.gitlint @@ -0,0 +1,9 @@ +[general] +ignore=body-is-missing,T3 +ignore-stdin=true + +[title-match-regex] +regex=[A-Z] + +[author-valid-email] +regex=[^@]+@k-space.ee diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..23d9eb4 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,16 @@ +repos: +- repo: https://github.com/PyCQA/flake8 + rev: 3.9.2 + hooks: + - id: flake8 + additional_dependencies: [flake8-typing-imports==1.10.0,flake8-quotes==3.2.0] + +- repo: https://github.com/jorisroovers/gitlint + rev: v0.15.1 + hooks: + - id: gitlint + +- repo: https://github.com/Lucas-C/pre-commit-hooks-nodejs + rev: v1.1.1 + hooks: + - id: dockerfile_lint diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..377df85 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,4 @@ +FROM python:3 +RUN pip3 install motor sanic +ADD wildduck_exporter.py /wildduck_exporter.py +ENTRYPOINT /wildduck_exporter.py diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..637e2d6 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,8 @@ +version: '3.7' +services: + app: + image: kspaceee/wildduck-exporter:latest + network_mode: host + env_file: .env + build: + context: . diff --git a/wildduck_exporter.py b/wildduck_exporter.py new file mode 100755 index 0000000..17aed4e --- /dev/null +++ b/wildduck_exporter.py @@ -0,0 +1,60 @@ +#!/usr/bin/env python3 +import os +from motor.motor_asyncio import AsyncIOMotorClient +from sanic import Sanic, response, exceptions + +MONGO_URI = os.getenv("MONGO_URI") +if not MONGO_URI: + raise ValueError("No MONGO_URI specified") + +PROMETHEUS_BEARER_TOKEN = os.getenv("PROMETHEUS_BEARER_TOKEN") +if not PROMETHEUS_BEARER_TOKEN: + raise ValueError("No PROMETHEUS_BEARER_TOKEN specified") + +app = Sanic("exporter") + + +@app.listener("before_server_start") +async def setup_db(app, loop): + app.ctx.db = AsyncIOMotorClient(MONGO_URI).get_default_database() + + +async def wrap(i, prefix="wildduck_"): + metrics_seen = set() + async for name, tp, value, labels in i: + if name not in metrics_seen: + yield "# TYPE %s %s" % (name, tp) + metrics_seen.add(name) + yield "%s%s %d" % ( + prefix + name, + ("{%s}" % ",".join(["%s=\"%s\"" % j for j in labels.items()]) if labels else ""), + value) + + +async def fetch(): + async for u in app.ctx.db.users.find(): + labels = { + "username": u["username"], + "email": u["address"], + } + if u["lastLogin"]["time"]: + yield "last_login", "gauge", u["lastLogin"]["time"].timestamp(), labels + if u["storageUsed"]: + yield "storage_used", "gauge", u["storageUsed"], labels + if u["targets"]: + yield "forwarding_addresses", "gauge", len(u["targets"]), labels + yield "account_enabled", "gauge", not u["disabled"], labels + + +@app.route("/metrics") +async def view_export(request): + if request.token != PROMETHEUS_BEARER_TOKEN: + raise exceptions.Forbidden("Invalid bearer token") + + async def streaming_fn(response): + async for line in wrap(fetch()): + await response.write(line + "\n") + + return response.stream(streaming_fn, content_type="text/plain") + +app.run(port=3001)