|
@@ -1,4 +1,8 @@
|
1
|
|
-from flask import Flask, abort, request
|
|
1
|
+import os
|
|
2
|
+
|
|
3
|
+from collections import deque
|
|
4
|
+from flask import Flask, abort, request, Response, render_template
|
|
5
|
+from functools import wraps
|
2
|
6
|
|
3
|
7
|
from lineandsinker.common import get_hook_key
|
4
|
8
|
from lineandsinker.services import services
|
|
@@ -10,11 +14,13 @@ def refresh_services():
|
10
|
14
|
|
11
|
15
|
|
12
|
16
|
refresh_services()
|
|
17
|
+history = deque(maxlen=100)
|
13
|
18
|
app = Flask(__name__)
|
14
|
19
|
|
15
|
20
|
|
16
|
21
|
def handle_events(events):
|
17
|
22
|
for event in events:
|
|
23
|
+ history.append(event)
|
18
|
24
|
if event["type"] == "git.push":
|
19
|
25
|
services["jenkins"].build_job_by_scm_url(event["repo"]["urls"])
|
20
|
26
|
if event["repo"]["public"]:
|
|
@@ -35,7 +41,7 @@ def handle_events(events):
|
35
|
41
|
|
36
|
42
|
@app.route("/")
|
37
|
43
|
def handle_index():
|
38
|
|
- return app.send_static_file("index.html")
|
|
44
|
+ return render_template("index.html")
|
39
|
45
|
|
40
|
46
|
|
41
|
47
|
@app.route("/hooks/<service>/<path:identifier>/<hash>", methods=["GET", "POST"])
|
|
@@ -44,10 +50,30 @@ def handle_hook(service, identifier, hash):
|
44
|
50
|
|
45
|
51
|
expected_hash = get_hook_key(service, identifier)
|
46
|
52
|
if hash != expected_hash:
|
|
53
|
+ handle_events(
|
|
54
|
+ [
|
|
55
|
+ {
|
|
56
|
+ "type": "lineandsinker.badhash",
|
|
57
|
+ "service": service,
|
|
58
|
+ "hash": hash,
|
|
59
|
+ "body": request.get_data(as_text=True),
|
|
60
|
+ }
|
|
61
|
+ ]
|
|
62
|
+ )
|
47
|
63
|
app.logger.info(f"Hash not valid. Expected: {expected_hash}, got {hash}")
|
48
|
64
|
abort(403)
|
49
|
65
|
|
50
|
66
|
if service not in services:
|
|
67
|
+ handle_events(
|
|
68
|
+ [
|
|
69
|
+ {
|
|
70
|
+ "type": "lineandsinker.badservice",
|
|
71
|
+ "service": service,
|
|
72
|
+ "known_services": list(services.keys()),
|
|
73
|
+ "body": request.get_data(as_text=True),
|
|
74
|
+ }
|
|
75
|
+ ]
|
|
76
|
+ )
|
51
|
77
|
app.logger.info(f"Unknown service {service}, known: {services.keys()}")
|
52
|
78
|
abort(404)
|
53
|
79
|
|
|
@@ -56,5 +82,36 @@ def handle_hook(service, identifier, hash):
|
56
|
82
|
return "", 200
|
57
|
83
|
|
58
|
84
|
|
|
85
|
+if "LAS_ADMIN_PASSWORD" in os.environ:
|
|
86
|
+
|
|
87
|
+ def requires_auth(f):
|
|
88
|
+ @wraps(f)
|
|
89
|
+ def decorated(*args, **kwargs):
|
|
90
|
+ auth = request.authorization
|
|
91
|
+ if (
|
|
92
|
+ not auth
|
|
93
|
+ or auth.username != "admin"
|
|
94
|
+ or auth.password != os.environ["LAS_ADMIN_PASSWORD"]
|
|
95
|
+ ):
|
|
96
|
+ return Response(
|
|
97
|
+ "Restricted resource",
|
|
98
|
+ 401,
|
|
99
|
+ {"WWW-Authenticate": 'Basic realm="Login Required"'},
|
|
100
|
+ )
|
|
101
|
+ return f(*args, **kwargs)
|
|
102
|
+
|
|
103
|
+ return decorated
|
|
104
|
+
|
|
105
|
+ @app.route("/admin")
|
|
106
|
+ @requires_auth
|
|
107
|
+ def handle_admin():
|
|
108
|
+ return render_template("admin.html", events=history)
|
|
109
|
+
|
|
110
|
+ @app.route("/admin/hash", methods=["POST"])
|
|
111
|
+ @requires_auth
|
|
112
|
+ def handle_admin_hash():
|
|
113
|
+ return get_hook_key(request.form["service"], request.form["identifier"]), 200
|
|
114
|
+
|
|
115
|
+
|
59
|
116
|
if __name__ == "__main__":
|
60
|
117
|
app.run("0.0.0.0")
|