diff --git a/deployment.yaml b/deployment.yaml new file mode 100644 index 0000000..48af804 --- /dev/null +++ b/deployment.yaml @@ -0,0 +1,44 @@ +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: wildduck-exporter + namespace: wildduck +spec: + replicas: 1 + selector: + matchLabels: + app: wildduck-exporter + template: + metadata: + labels: + app: wildduck-exporter + spec: + containers: + - name: wildduck-exporter + image: harbor.k-space.ee/k-space/wildduck-exporter + securityContext: + readOnlyRootFilesystem: true + runAsNonRoot: true + runAsUser: 65534 + command: + - /wildduck_exporter.py + args: + - info + - accounting + ports: + - containerPort: 3001 + name: metrics + env: + - name: MONGODB_HOST + valueFrom: + secretKeyRef: + name: wildduck + key: MONGO_URI + - name: PROMETHEUS_BEARER_TOKEN + valueFrom: + secretKeyRef: + name: wildduck-exporter + key: PROMETHEUS_BEARER_TOKEN + imagePullSecrets: + - name: regcred diff --git a/skaffold.yaml b/skaffold.yaml new file mode 100644 index 0000000..bf41771 --- /dev/null +++ b/skaffold.yaml @@ -0,0 +1,18 @@ +--- +apiVersion: skaffold/v3 +kind: Config +metadata: + name: wildduck-exporter +build: + artifacts: + - docker: + dockerfile: Dockerfile + image: harbor.k-space.ee/k-space/wildduck-exporter +deploy: + kubectl: + flags: + global: + - --namespace=wildduck +manifests: + rawYaml: + - deployment.yaml diff --git a/wildduck_exporter.py b/wildduck_exporter.py index bc08ce0..c8a3a3d 100755 --- a/wildduck_exporter.py +++ b/wildduck_exporter.py @@ -1,7 +1,9 @@ #!/usr/bin/env python3 import os +import sys from motor.motor_asyncio import AsyncIOMotorClient -from sanic import Sanic, response, exceptions +from sanic import Sanic, exceptions +from sanic.log import logger MONGODB_HOST = os.getenv("MONGODB_HOST") if not MONGODB_HOST: @@ -12,6 +14,7 @@ if not PROMETHEUS_BEARER_TOKEN: raise ValueError("No PROMETHEUS_BEARER_TOKEN specified") app = Sanic("exporter") +usernames = list(set(sys.argv[1:])) @app.listener("before_server_start") @@ -32,30 +35,44 @@ async def wrap(i, prefix="wildduck_"): async def fetch(): - async for u in app.ctx.db.users.find(): + last_login = {} + user_labels = {} + args = [] + if usernames: + args.append({"username": {"$in": usernames}}) + async for u in app.ctx.db.users.find(*args): labels = { "username": u["username"], "email": u["address"], } + user_labels[u["_id"]] = labels + if u["lastLogin"]["time"]: - yield "last_login", "gauge", u["lastLogin"]["time"].timestamp(), labels + last_login[u["_id"]] = u["lastLogin"]["time"] + 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 + # Merge application specific passwords last used timestamps + async for a in app.ctx.db.asps.find({"user": {"$in": list(last_login.keys())}}): + if a["used"] > last_login[a["user"]]: + last_login[a["user"]] = a["used"] + + for user, dt in last_login.items(): + yield "last_login", "gauge", dt.timestamp(), user_labels[user] + @app.route("/metrics") async def view_export(request): if request.token != PROMETHEUS_BEARER_TOKEN: raise exceptions.Forbidden("Invalid bearer token") + response = await request.respond(content_type="text/plain") + async for line in wrap(fetch()): + await response.send(line + "\n") - 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") if __name__ == "__main__": app.run(host="0.0.0.0", port=3001, single_process=True)