-
Notifications
You must be signed in to change notification settings - Fork 1
/
github-webhook.py
171 lines (149 loc) · 6.75 KB
/
github-webhook.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
import hashlib
import hmac
import logging
import subprocess
import traceback
import requests
from flask import url_for
from pkg_resources import Requirement, resource_filename
import yaml
from threading import Thread
from flask import Flask, request, jsonify, Response
# configFileName = resource_filename(Requirement.parse("sonata_editor"), "deployment.yml")
CONFIG = yaml.safe_load(open("deployment.yml"))
DEPLOYMENT_DIVIDER = "##########################################################"
# set up logging to file - see previous section for more details
logging.basicConfig(level=logging.INFO,
format='%(asctime)s %(name)-12s %(levelname)-8s %(message)s',
datefmt='%m-%d %H:%M',
filename='deployment.log',
filemode='w')
# define a Handler which writes INFO messages or higher to the sys.stderr
console = logging.StreamHandler()
console.setLevel(logging.INFO)
# set a format which is simpler for console use
formatter = logging.Formatter('%(name)-12s: %(levelname)-8s %(message)s')
# tell the handler to use this format
console.setFormatter(formatter)
# add the handler to the root logger
logging.getLogger('').addHandler(console)
logger = logging.getLogger("github-webhook")
app = Flask(__name__)
def verify_hmac_hash(data, signature):
github_secret = bytes(CONFIG['github-secret'], 'UTF-8')
mac = hmac.new(github_secret, msg=data, digestmod=hashlib.sha1)
return hmac.compare_digest('sha1=' + mac.hexdigest(), signature)
@app.route("/latest_build_log")
def show_latest_log():
with open("deployment.log") as logfile:
return logfile.read().split(DEPLOYMENT_DIVIDER)[-1].replace("\n", "<br/>")
@app.route("/payload", methods=['POST'])
def github_payload():
logger.info(DEPLOYMENT_DIVIDER)
build_log_url = url_for("show_latest_log", _external=True)
try:
signature = request.headers.get('X-Hub-Signature')
data = request.data
if verify_hmac_hash(data, signature):
if 'X-GitHub-Event' in request.headers:
if request.headers.get('X-GitHub-Event') == "ping":
return jsonify({'msg': 'Ok'})
if request.headers.get('X-GitHub-Event') == "deployment":
payload = request.get_json()
logger.debug(str(payload))
Thread(target=redeploy, args=(payload, build_log_url)).start()
return Response("Starting deployment", mimetype='text/plain')
else:
logger.warn(request.headers.get('X-GitHub-Event'))
return jsonify({'msg': 'ok thanks for letting me know!'})
else:
return jsonify({'msg': 'hmmm somthing is wrong'}), 500
else:
logger.error("Invalid signature, aborting")
return jsonify({'msg': 'invalid hash'}), 401
except Exception as err:
traceback.print_exc()
def redeploy(payload, build_log_url):
logger.info("Starting redeployment script")
for command in CONFIG['deployment-script']:
required = True
if 'required' in command:
required = command['required']
if 'update-status' in command:
update_deployment_status(payload, "pending", command['update-status'], build_log_url)
elif 'request' in command:
if command['request'] is not None:
logger.info(command['request'])
try:
res = None
if command['method'] == 'GET':
res = requests.get(command['url'])
elif command['method'] == 'POST':
res = requests.post(command['url'])
if res is not None:
logger.info("Response was:" + res.text)
except Exception as err:
if required:
logger.exception("Code deployment failed")
update_deployment_status(payload, "failure", "Deployment failed, see log for details",
build_log_url)
return
else:
update_deployment_status(payload, "error", "Deployment failed, see log for details", build_log_url)
logger.exception("Code deployment had an error")
elif 'run' in command:
if command['run'] is not None:
logger.info(command['run'])
try:
if 'sync' in command:
runProcess(command['sync'].split())
elif 'async' in command:
runInBackground(command['async'].split())
except subprocess.CalledProcessError as error:
if required:
logger.exception("Code deployment failed")
update_deployment_status(payload, "failure", "Deployment failed, see log for details",
build_log_url)
return
except Exception as fail:
if required:
logger.exception("Code deployment failed")
update_deployment_status(payload, "failure",
"Deployment failed with statuscode {}".format(fail.args[1]), build_log_url)
return
update_deployment_status(payload, "success", "Deployment finished", build_log_url, CONFIG['environment_url'])
logger.info("Deploy finished!")
def update_deployment_status(payload, state, description, log_url, environment_url=None):
headers = {"Accept": "application/vnd.github.ant-man-preview+json",
"Authorization": "token " + CONFIG['github-token']}
jsondata = {"state": state,
"description": description,
"log_url": log_url}
if environment_url is not None:
jsondata['environment_url'] = environment_url
res = requests.post(payload["deployment"]["url"] + "/statuses",
json=jsondata,
headers=headers)
logger.debug(res.text)
def runInBackground(exe):
subprocess.Popen(exe)
logger.info("Starting " + str(exe))
def runProcess(exe):
logger.info("Running " + str(exe))
p = subprocess.Popen(exe, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
while True:
retcode = p.poll() # returns None while subprocess is running
line = p.stdout.readline()
try:
line = line.decode("utf-8")[::-1].replace('\n', '', 1)[::-1]
if not line == "":
logger.info(line)
except:
logger.info(line)
if retcode is not None:
logger.info("Returncode: {}".format(retcode))
break
if not retcode == 0:
raise Exception(retcode)
if __name__ == "__main__":
app.run(host="0.0.0.0", port=5050)