From 354ca56a80f1297599e9d13afa89cc05fe28fd11 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hannes=20K=C3=B6rber?= Date: Sat, 5 Sep 2020 13:20:22 +0200 Subject: [PATCH] Initial commit --- .drone.yml | 13 +++++ Dockerfile | 24 +++++++++ README.md | 5 ++ restic-monitor.py | 127 ++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 169 insertions(+) create mode 100644 .drone.yml create mode 100644 Dockerfile create mode 100644 README.md create mode 100755 restic-monitor.py diff --git a/.drone.yml b/.drone.yml new file mode 100644 index 0000000..a50c318 --- /dev/null +++ b/.drone.yml @@ -0,0 +1,13 @@ +kind: pipeline +type: kubernetes +name: default +steps: +- name: image + image: registry.hkoerber.de/drone-kaniko:v1.0.0 + pull: true + settings: + dockerfile: Dockerfile + registry: registry.hkoerber.de + repo: prometheus-restic-backblaze + tags: + - ${DRONE_COMMIT_SHA} diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..60e1e5f --- /dev/null +++ b/Dockerfile @@ -0,0 +1,24 @@ +FROM debian:buster + +RUN : \ + && apt-get update \ + && apt-get install -y \ + curl \ + python3 \ + python3-prometheus-client \ + python3-yaml \ + restic \ + && apt-get -y clean + +RUN : \ + && curl -s -L -o /usr/local/bin/dumb-init https://github.com/Yelp/dumb-init/releases/download/v1.2.2/dumb-init_1.2.2_amd64 \ + && chmod +x /usr/local/bin/dumb-init + +RUN : \ + && curl -s -L https://github.com/restic/restic/releases/download/v0.9.6/restic_0.9.6_linux_amd64.bz2 \ + | bzip2 > /usr/local/bin/restic \ + && chmod +x /usr/local/bin/restic + +COPY ./restic-monitor.py /usr/local/bin/restic-monitor + +CMD ["/usr/local/bin/dumb-init", "--", "/usr/local/bin/restic-monitor", "/etc/restic-backblaze-config.yml"] diff --git a/README.md b/README.md new file mode 100644 index 0000000..5f5b5cc --- /dev/null +++ b/README.md @@ -0,0 +1,5 @@ +``` +$ docker build -t prometheus-restic-backblaze . + +$ docker run -v $(pwd)/config.yml:/etc/restic-config.yml prometheus-restic-backblaze +``` diff --git a/restic-monitor.py b/restic-monitor.py new file mode 100755 index 0000000..1dfcef2 --- /dev/null +++ b/restic-monitor.py @@ -0,0 +1,127 @@ +#!/usr/bin/env python3 + +import os +import json +import sys +import datetime +import subprocess +import time + +import prometheus_client +import yaml + +with open(sys.argv[1], 'r') as fd: + config = yaml.safe_load(fd) + +output = {} + +class ResticBackblazeCollector(object): + def __init__(self): + self._last_update = 0 + + def get_info(self): + for repo in config['repositories']: + print("Collecting snapshots from backblaze") + cmd = [ + "/usr/bin/restic", + "--repo", "b2:{bucket}:{folder}".format( + bucket=repo['bucket'], + folder=repo['folder']), + "snapshots", + "--json", + "--no-cache", + "--no-lock" + ] + + env = { + "B2_ACCOUNT_ID": repo["b2_account_id"], + "B2_ACCOUNT_KEY": repo["b2_account_key"], + "RESTIC_PASSWORD": repo["restic_password"] + } + + restic_cmd = subprocess.run( + cmd, + stdout=subprocess.PIPE, + check=True, + env=env + ) + print("Done") + + snapshots = json.loads(restic_cmd.stdout) + + def _get_backup_id(snapshot): + hostname = snapshot['hostname'] + paths = ','.join(snapshot['paths']) + return (hostname, paths) + + def _parse_timestamp(timestamp): + # get rid of everything after . + timestamp = timestamp.split('.')[0] + return datetime.datetime.strptime( + timestamp, + '%Y-%m-%dT%H:%M:%S' + ) + + distinct_backups = [] + for snapshot in snapshots: + backup = _get_backup_id(snapshot) + if backup not in distinct_backups: + distinct_backups.append(backup) + + latest_snapshots = [] + for backup in distinct_backups: + latest = None + latest_timestamp = datetime.datetime.fromtimestamp(0) # unixtime 0 + for snapshot in snapshots: + if _get_backup_id(snapshot) == backup: + timestamp = _parse_timestamp(snapshot['time']) + if timestamp > latest_timestamp: + latest = snapshot + latest_timestamp = timestamp + latest_snapshots.append(latest) + + output[repo['name']] = [] + for snapshot in latest_snapshots: + output[repo['name']].append({ + "host": snapshot["hostname"], + "paths": ",".join(snapshot["paths"]), + "timestamp": _parse_timestamp(snapshot['time']).timestamp(), + }) + + return output + + + def collect(self): + called = time.time() + + age = called - self._last_update + if age > 24 * 60 * 60: + self._output = self.get_info() + self._last_update = time.time() + else: + print("Using cached metrics") + + metric_timestamp = prometheus_client.core.GaugeMetricFamily( + 'restic_latest_snapshot_timestamp', + "Timestamp of the latest restic snapshot", + labels=["repository", "host", "paths"]) + + for repo, info in self._output.items(): + for snapshot in info: + metric_timestamp.add_metric( + labels=[ + repo, + snapshot["host"], + snapshot["paths"] + ], + value=snapshot["timestamp"] + ) + + yield metric_timestamp + +prometheus_client.REGISTRY.register(ResticBackblazeCollector()) + +if __name__ == '__main__': + prometheus_client.start_http_server(int(os.environ["LISTEN_PORT"])) + while True: + time.sleep(1)