WebHook broker that accepts notifications from multiple platforms and performs simple actions in response
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

main.py 3.5KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116
  1. import os
  2. import re
  3. import socket
  4. from functools import wraps
  5. from flask import Flask, abort, request, Response
  6. from lineandsinker.common import get_hook_key
  7. from lineandsinker.services import gitea_factory, jenkins_factory
  8. url_pattern = re.compile(
  9. "^/hooks/(?P<service>[^/]+)/(?P<identifier>.*)/(?P<key>[^/]+)$"
  10. )
  11. def authenticate(f):
  12. @wraps(f)
  13. def wrapper(*args, **kwargs):
  14. path = request.path
  15. match = url_pattern.match(path)
  16. if not match:
  17. return Response("Bad request", 400)
  18. expected_key = get_hook_key(match.group("service"), match.group("identifier"))
  19. if expected_key != match.group("key"):
  20. app.logger.info(f"Bad request to {path}: expected key {expected_key}")
  21. return Response("Invalid key", 403)
  22. return f(*args, **kwargs)
  23. return wrapper
  24. def reportbot_announce(message):
  25. try:
  26. with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
  27. host = os.environ["LAS_REPORTBOT_ADDRESS"].split(":")
  28. sock.connect((host[0], int(host[1])))
  29. sock.sendall(
  30. f"{os.environ['LAS_REPORTBOT_PREFIX']} {message}\n".encode("utf-8")
  31. )
  32. app.logger.info(f"Report bot response: {sock.recv(512)}")
  33. except:
  34. app.logger.exception("Unable to send report bot message")
  35. jenkins_service = jenkins_factory()
  36. gitea_service = gitea_factory()
  37. repos = dict((name, [ssh, clone]) for name, ssh, clone in gitea_service.get_repos())
  38. jobs = list(jenkins_service.get_jobs())
  39. app = Flask(__name__)
  40. @app.route("/")
  41. def handle_index():
  42. return app.send_static_file("index.html")
  43. @app.route("/hooks/gitea/<path:repo>/<hash>", methods=["POST"])
  44. @authenticate
  45. def handle_hook_gitea(repo):
  46. app.logger.info(f"Received hook for repo {repo}")
  47. if repo not in repos:
  48. app.logger.info(f"Repository not found. Known repos: {repos.keys()}")
  49. abort(404)
  50. if request.headers.get("X-Gitea-Event") == "push":
  51. urls = repos[repo]
  52. for name, spec, url in jobs:
  53. if url in urls:
  54. # TODO: Check branches
  55. app.logger.info(f"Found matching job: {name} with URL {url}")
  56. jenkins_service.build_job(name)
  57. data = request.get_json()
  58. if not data["repository"]["private"]:
  59. repo = data["repository"]["full_name"]
  60. commits = len(data["commits"])
  61. compare = data["compare_url"]
  62. pusher = data["pusher"]["login"]
  63. reportbot_announce(
  64. f"\002[git]\002 {pusher} pushed {commits} commit{'s' if commits != 1 else ''} to {repo}: {compare}"
  65. )
  66. for commit in data["commits"][:3]:
  67. reportbot_announce(
  68. f"\002[git]\002 {commit['id'][:10]}: {commit['message'][:100]}"
  69. )
  70. return "", 204
  71. @app.route("/hooks/docker/registry/<hash>", methods=["GET", "POST"])
  72. @authenticate
  73. def handle_docker_registry():
  74. for event in request.get_json()["events"]:
  75. if (
  76. event["action"] == "push"
  77. and "vnd.docker.distribution.manifest" in event["target"]["mediaType"]
  78. and "tag" in event["target"]
  79. ):
  80. repo = event["target"]["repository"]
  81. tag = event["target"]["tag"]
  82. host = event["request"]["host"]
  83. user = event["actor"]["name"]
  84. reportbot_announce(
  85. f"\002[registry]\002 New manifest pushed to {host}/{repo}:{tag} by {user}"
  86. )
  87. return "", 204
  88. app.run("0.0.0.0")