#!/usr/bin/env python3 import datetime import hashlib import hmac import json import pathlib import re import subprocess import sys import time import urllib.error import urllib.request BASE = pathlib.Path("/srv/graphite") AGENT_ENV = pathlib.Path("/etc/graphite/agent.env") HUB = "graphite-hub" LINE = re.compile(r"""^([A-Z_][A-Z0-9_]*)=(?:'([^']*)'|"((?:[^"\\]|\\.)*)"|(.*))$""") ESCAPES = {"n": "\n", "r": "\r", "t": "\t"} def settings(path): found = {} for line in path.read_text(encoding="utf-8").splitlines() if path.exists() else []: match = LINE.match(line) if match: single, double, plain = match.group(2, 3, 4) found[match[1]] = single if single is not None else re.sub( r"\\(.)", lambda escape: ESCAPES.get(escape[1], escape[1]), double ) if double is not None else plain.strip() return found def docker(*args): return subprocess.run(["docker", *args], capture_output=True, text=True, timeout=30).stdout.strip() def hub_address(): container = docker( "ps", "-q", "--filter", f"label=com.docker.compose.project={HUB}", "--filter", "label=com.docker.compose.service=app", ).split("\n")[0] if not container: return None return docker("inspect", "-f", '{{with index .NetworkSettings.Networks "edge"}}{{.IPAddress}}{{end}}', container) or None def health(host): started = time.monotonic() try: probe = urllib.request.Request(f"https://{host}/", headers={"User-Agent": "project-graphite-deploy-report"}) with urllib.request.urlopen(probe, timeout=10) as response: status = response.status except urllib.error.HTTPError as error: status = error.code except (urllib.error.URLError, OSError): status = None return status, round((time.monotonic() - started) * 1000) def main(name, started, exit_status): project = settings(BASE / name / "values") if "HOST" not in project: print(f"graphite-report: {name} has no host to report") return 0 secret = settings(AGENT_ENV).get("DEPLOY_REPORT_SECRET") address = hub_address() if not secret or not address: print(f"graphite-report: nothing reported for {name}") return 0 status, ms = health(project["HOST"]) body = json.dumps({ "project": name, "tag": project.get("IMAGE_TAG", "unknown"), "startedAt": started, "finishedAt": datetime.datetime.now(datetime.UTC).strftime("%Y-%m-%dT%H:%M:%SZ"), "ok": exit_status == "0", **({"healthStatus": status} if status is not None else {}), "healthMs": ms, }).encode() stamp = str(int(time.time())) request = urllib.request.Request( f"http://{address}:3000/internal/deploys", data=body, method="POST", headers={ "Content-Type": "application/json", "X-Graphite-Timestamp": stamp, "X-Graphite-Signature": hmac.new(secret.encode(), stamp.encode() + b"." + body, hashlib.sha256).hexdigest(), }, ) with urllib.request.urlopen(request, timeout=10) as response: print(f"graphite-report: {name} reported to the hub ({response.status})") return 0 if __name__ == "__main__": sys.exit(main(*sys.argv[1:4]))