Browse Source

Refactor hook handling.

Have one route for all hooks that delegates to the appropriate
service. Services yield events that are then actioned somehow
(for now in a big ugly method).
master
Chris Smith 5 years ago
parent
commit
bb8be15375

+ 15
- 0
lineandsinker/services/docker.py View File

@@ -4,3 +4,18 @@ from .service import Service
4 4
 class Docker(Service):
5 5
     def __init__(self):
6 6
         super().__init__("docker")
7
+
8
+    def accept_hook(self, identifier, request):
9
+        for event in request.get_json()["events"]:
10
+            if (
11
+                event["action"] == "push"
12
+                and "vnd.docker.distribution.manifest" in event["target"]["mediaType"]
13
+                and "tag" in event["target"]
14
+            ):
15
+                yield {
16
+                    "type": "docker.push",
17
+                    "repo": event["target"]["repository"],
18
+                    "tag": event["target"]["tag"],
19
+                    "host": event["request"]["host"],
20
+                    "user": event["actor"]["name"],
21
+                }

+ 31
- 2
lineandsinker/services/gitea.py View File

@@ -1,4 +1,4 @@
1
-from jsonref import requests
1
+import requests
2 2
 
3 3
 from ..common import get_hook_url
4 4
 from .service import Service
@@ -10,8 +10,37 @@ class Gitea(Service):
10 10
         self._url = url
11 11
         self._token = token
12 12
         self._install_hooks = install_hooks
13
+        self._repos = {}
13 14
 
14
-    def get_repos(self):
15
+    def refresh(self):
16
+        self._repos = dict(
17
+            (name, [ssh, clone]) for name, ssh, clone in self._get_repos()
18
+        )
19
+
20
+    def accept_hook(self, identifier, request):
21
+        if identifier not in self._repos:
22
+            return
23
+
24
+        if request.headers.get("X-Gitea-Event") == "push":
25
+            urls = self._repos[identifier]
26
+            data = request.get_json()
27
+
28
+            yield {
29
+                "type": "git.push",
30
+                "repo": {
31
+                    "name": data["repository"]["full_name"],
32
+                    "public": data["repository"]["private"],
33
+                    "urls": urls,
34
+                },
35
+                "commits": [
36
+                    {"id": c["id"][:10], "message": c["message"]}
37
+                    for c in data["commits"]
38
+                ],
39
+                "compare_url": data["compare_url"],
40
+                "user": data["pusher"]["login"],
41
+            }
42
+
43
+    def _get_repos(self):
15 44
         repos = self._request("get", f"user/repos").json()
16 45
         for repo in repos:
17 46
             if self._install_hooks:

+ 9
- 3
lineandsinker/services/jenkins.py View File

@@ -11,8 +11,12 @@ class Jenkins(Service):
11 11
     def __init__(self, url, username, password):
12 12
         super().__init__("jenkins")
13 13
         self._server = jenkins.Jenkins(url, username=username, password=password)
14
+        self._jobs = []
14 15
 
15
-    def get_jobs(self):
16
+    def refresh(self):
17
+        self._jobs = list(self._get_jobs())
18
+
19
+    def _get_jobs(self):
16 20
         for job in self._server.get_all_jobs():
17 21
             name = job["fullname"]
18 22
             config = BeautifulSoup(self._server.get_job_config(name), features="xml")
@@ -20,5 +24,7 @@ class Jenkins(Service):
20 24
                 branch_spec = git_config.find("branches").find("name").text
21 25
                 yield name, branch_spec, git_config.find("url").text
22 26
 
23
-    def build_job(self, name):
24
-        self._server.build_job(name)
27
+    def build_job_by_scm_url(self, urls):
28
+        for job, branch, url in self._jobs:
29
+            if url in urls:
30
+                self._server.build_job(job)

+ 3
- 0
lineandsinker/services/service.py View File

@@ -2,5 +2,8 @@ class Service:
2 2
     def __init__(self, type):
3 3
         self.type = type
4 4
 
5
+    def refresh(self):
6
+        pass
7
+
5 8
     def accept_hook(self, identifier, request):
6 9
         pass

+ 31
- 75
main.py View File

@@ -1,38 +1,31 @@
1
-import re
2
-from functools import wraps
3
-
4
-from flask import Flask, abort, request, Response
1
+from flask import Flask, abort, request
5 2
 
6 3
 from lineandsinker.common import get_hook_key
7 4
 from lineandsinker.services import services
8 5
 
9
-url_pattern = re.compile(
10
-    "^/hooks/(?P<service>[^/]+)/(?P<identifier>.*)/(?P<key>[^/]+)$"
11
-)
12
-
13
-
14
-def authenticate(f):
15
-    @wraps(f)
16
-    def wrapper(*args, **kwargs):
17
-        path = request.path
18
-        match = url_pattern.match(path)
6
+for service in services.values():
7
+    service.refresh()
19 8
 
20
-        if not match:
21
-            return Response("Bad request", 400)
22
-
23
-        expected_key = get_hook_key(match.group("service"), match.group("identifier"))
24
-        if expected_key != match.group("key"):
25
-            app.logger.info(f"Bad request to {path}: expected key {expected_key}")
26
-            return Response("Invalid key", 403)
27
-
28
-        return f(*args, **kwargs)
9
+app = Flask(__name__)
29 10
 
30
-    return wrapper
31 11
 
12
+def handle_events(events):
13
+    for event in events:
14
+        if event["type"] == "git.push":
15
+            services["jenkins"].build_job_by_scm_url(event["repo"]["urls"])
16
+            if event["repo"]["public"]:
17
+                services["reportbot"].announce(
18
+                    f"\002[git]\002 {event['user']} pushed {len(event['commits'])} commit{'s' if len(event['commits']) != 1 else ''} to {event['repo']['name']}: {event['compare_url']}"
19
+                )
32 20
 
33
-repos = dict((name, [ssh, clone]) for name, ssh, clone in services["gitea"].get_repos())
34
-jobs = list(services["jenkins"].get_jobs())
35
-app = Flask(__name__)
21
+                for commit in event["commits"][:3]:
22
+                    services["reportbot"].announce(
23
+                        f"\002[git]\002 {commit['id']}: {commit['message'][:100]}"
24
+                    )
25
+        elif event["type"] == "docker.push":
26
+            services["reportbot"].announce(
27
+                f"\002[registry]\002 New manifest pushed to {event['host']}/{event['repo']}:{event['tag']} by {event['user']}"
28
+            )
36 29
 
37 30
 
38 31
 @app.route("/")
@@ -40,57 +33,20 @@ def handle_index():
40 33
     return app.send_static_file("index.html")
41 34
 
42 35
 
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}")
36
+@app.route("/hooks/<service>/<path:identifier>/<hash>", methods=["GET", "POST"])
37
+def handle_hook(service, identifier, hash):
38
+    app.logger.info(f"Received hook for {service} with identifier {hash}")
47 39
 
48
-    if repo not in repos:
49
-        app.logger.info(f"Repository not found. Known repos: {repos.keys()}")
50
-        abort(404)
51
-
52
-    if request.headers.get("X-Gitea-Event") == "push":
53
-        urls = repos[repo]
54
-        for name, spec, url in jobs:
55
-            if url in urls:
56
-                # TODO: Check branches
57
-                app.logger.info(f"Found matching job: {name} with URL {url}")
58
-                services["jenkins"].build_job(name)
59
-
60
-        data = request.get_json()
61
-        if not data["repository"]["private"]:
62
-            repo = data["repository"]["full_name"]
63
-            commits = len(data["commits"])
64
-            compare = data["compare_url"]
65
-            pusher = data["pusher"]["login"]
66
-
67
-            services["reportbot"].announce(
68
-                f"\002[git]\002 {pusher} pushed {commits} commit{'s' if commits != 1 else ''} to {repo}: {compare}"
69
-            )
70
-            for commit in data["commits"][:3]:
71
-                services["reportbot"].announce(
72
-                    f"\002[git]\002 {commit['id'][:10]}: {commit['message'][:100]}"
73
-                )
74
-
75
-    return "", 204
40
+    expected_hash = get_hook_key(service, identifier)
41
+    if hash != expected_hash:
42
+        app.logger.info(f"Hash not valid. Expected: {expected_hash}, got {hash}")
43
+        abort(403)
76 44
 
45
+    if service not in services:
46
+        app.logger.info(f"Unknown service {service}, known: {services.keys()}")
47
+        abort(404)
77 48
 
78
-@app.route("/hooks/docker/registry/<hash>", methods=["GET", "POST"])
79
-@authenticate
80
-def handle_docker_registry():
81
-    for event in request.get_json()["events"]:
82
-        if (
83
-            event["action"] == "push"
84
-            and "vnd.docker.distribution.manifest" in event["target"]["mediaType"]
85
-            and "tag" in event["target"]
86
-        ):
87
-            repo = event["target"]["repository"]
88
-            tag = event["target"]["tag"]
89
-            host = event["request"]["host"]
90
-            user = event["actor"]["name"]
91
-            services["reportbot"].announce(
92
-                f"\002[registry]\002 New manifest pushed to {host}/{repo}:{tag} by {user}"
93
-            )
49
+    handle_events(services[service].accept_hook(identifier, request))
94 50
 
95 51
     return "", 204
96 52
 

Loading…
Cancel
Save