Package coprs :: Package views :: Package webhooks_ns :: Module webhooks_general
[hide private]
[frames] | no frames]

Source Code for Module coprs.views.webhooks_ns.webhooks_general

  1  import flask 
  2  from functools import wraps 
  3   
  4  from coprs import db, app 
  5   
  6  from coprs.logic.builds_logic import BuildsLogic 
  7  from coprs.logic.complex_logic import ComplexLogic 
  8  from coprs.logic.packages_logic import PackagesLogic 
  9   
 10  from coprs.exceptions import ObjectNotFound 
 11   
 12  from coprs.views.webhooks_ns import webhooks_ns 
 13  from coprs.views.misc import page_not_found, access_restricted 
 14   
 15  import logging 
 16  import os 
 17  import tempfile 
 18  import shutil 
 19   
 20  log = logging.getLogger(__name__) 
21 22 23 -def skip_invalid_calls(route):
24 """ 25 A best effort attempt to drop hook callswhich should not obviously end up 26 with new build request (thus allocated build-id). 27 """ 28 @wraps(route) 29 def decorated_function(*args, **kwargs): 30 if 'X-GitHub-Event' in flask.request.headers: 31 event = flask.request.headers["X-GitHub-Event"] 32 if event == "ping": 33 return "SKIPPED\n", 200 34 return route(*args, **kwargs)
35 36 return decorated_function 37
38 39 -def copr_id_and_uuid_required(route):
40 @wraps(route) 41 def decorated_function(**kwargs): 42 if not 'copr_id' in kwargs or not 'uuid' in kwargs: 43 return 'COPR_ID_OR_UUID_TOKEN_MISSING\n', 400 44 45 copr_id = kwargs.pop('copr_id') 46 try: 47 copr = ComplexLogic.get_copr_by_id_safe(copr_id) 48 except ObjectNotFound: 49 return "PROJECT_NOT_FOUND\n", 404 50 51 if copr.webhook_secret != kwargs.pop('uuid'): 52 return "BAD_UUID\n", 403 53 54 return route(copr, **kwargs)
55 56 return decorated_function 57
58 59 -def package_name_required(route):
60 @wraps(route) 61 def decorated_function(copr, **kwargs): 62 if not 'package_name' in kwargs: 63 return 'PACKAGE_NAME_REQUIRED\n', 400 64 65 package_name = kwargs.pop('package_name') 66 try: 67 package = ComplexLogic.get_package_safe(copr.main_dir, package_name) 68 except ObjectNotFound: 69 return "PACKAGE_NOT_FOUND\n", 404 70 71 return route(copr, package, **kwargs)
72 73 return decorated_function 74
75 76 @webhooks_ns.route("/bitbucket/<int:copr_id>/<uuid>/", methods=["POST"]) 77 -def webhooks_bitbucket_push(copr_id, uuid):
78 # For the documentation of the data we receive see: 79 # https://confluence.atlassian.com/bitbucket/event-payloads-740262817.html 80 try: 81 copr = ComplexLogic.get_copr_by_id_safe(copr_id) 82 except ObjectNotFound: 83 return page_not_found("Project does not exist") 84 85 if copr.webhook_secret != uuid: 86 return access_restricted("This webhook is not valid") 87 88 try: 89 payload = flask.request.json 90 api_url = payload['repository']['links']['self']['href'] 91 clone_url = payload['repository']['links']['html']['href'] 92 commits = [] 93 ref_type = payload['push']['changes'][0]['new']['type'] 94 ref = payload['push']['changes'][0]['new']['name'] 95 try: 96 actor = payload['actor']['links']['html']['href'] 97 except KeyError: 98 actor = None 99 100 if ref_type == 'tag': 101 committish = ref 102 else: 103 committish = payload['push']['changes'][0]['new']['target']['hash'] 104 except KeyError: 105 return "Bad Request", 400 106 107 packages = PackagesLogic.get_for_webhook_rebuild( 108 copr_id, uuid, clone_url, commits, ref_type, ref 109 ) 110 111 for package in packages: 112 BuildsLogic.rebuild_package(package, {'committish': committish}, 113 submitted_by=actor) 114 115 db.session.commit() 116 117 return "OK", 200
118
119 120 @webhooks_ns.route("/github/<int:copr_id>/<uuid>/", methods=["POST"]) 121 -def webhooks_git_push(copr_id, uuid):
122 if flask.request.headers["X-GitHub-Event"] == "ping": 123 return "OK", 200 124 # For the documentation of the data we receive see: 125 # https://developer.github.com/v3/activity/events/types/#pushevent 126 try: 127 copr = ComplexLogic.get_copr_by_id_safe(copr_id) 128 except ObjectNotFound: 129 return page_not_found("Project does not exist") 130 131 if copr.webhook_secret != uuid: 132 return access_restricted("This webhook is not valid") 133 134 try: 135 payload = flask.request.json 136 clone_url = payload['repository']['clone_url'] 137 commits = [] 138 payload_commits = payload.get('commits', []) 139 for payload_commit in payload_commits: 140 commits.append({ 141 'added': payload_commit['added'], 142 'modified': payload_commit['modified'], 143 'removed': payload_commit['removed'], 144 }) 145 146 ref_type = payload.get('ref_type', '') 147 ref = payload.get('ref', '') 148 try: 149 sender = payload['sender']['url'] 150 except KeyError: 151 sender = None 152 except KeyError: 153 return "Bad Request", 400 154 155 packages = PackagesLogic.get_for_webhook_rebuild(copr_id, uuid, clone_url, commits, ref_type, ref) 156 157 committish = (ref if ref_type == 'tag' else payload.get('after', '')) 158 for package in packages: 159 BuildsLogic.rebuild_package(package, {'committish': committish}, 160 submitted_by=sender) 161 162 db.session.commit() 163 164 return "OK", 200
165
166 167 @webhooks_ns.route("/gitlab/<int:copr_id>/<uuid>/", methods=["POST"]) 168 -def webhooks_gitlab_push(copr_id, uuid):
169 # For the documentation of the data we receive see: 170 # https://gitlab.com/help/user/project/integrations/webhooks#events 171 try: 172 copr = ComplexLogic.get_copr_by_id_safe(copr_id) 173 except ObjectNotFound: 174 return page_not_found("Project does not exist") 175 176 if copr.webhook_secret != uuid: 177 return access_restricted("This webhook is not valid") 178 179 try: 180 payload = flask.request.json 181 clone_url = payload['project']['git_http_url'] 182 commits = [] 183 payload_commits = payload.get('commits', []) 184 for payload_commit in payload_commits: 185 commits.append({ 186 'added': payload_commit['added'], 187 'modified': payload_commit['modified'], 188 'removed': payload_commit['removed'], 189 }) 190 if payload['object_kind'] == 'tag_push': 191 ref_type = 'tag' 192 ref = os.path.basename(payload.get('ref', '')) 193 else: 194 ref_type = None 195 ref = payload.get('ref', '') 196 197 try: 198 submitter = 'gitlab.com:{}'.format(str(payload["user_username"])) 199 except KeyError: 200 submitter = None 201 202 except KeyError: 203 return "Bad Request", 400 204 205 packages = PackagesLogic.get_for_webhook_rebuild(copr_id, uuid, clone_url, commits, ref_type, ref) 206 207 committish = (ref if ref_type == 'tag' else payload.get('after', '')) 208 for package in packages: 209 BuildsLogic.rebuild_package(package, {'committish': committish}, 210 submitted_by=submitter) 211 212 db.session.commit() 213 214 return "OK", 200
215
216 217 -class HookContentStorage(object):
218 tmp = None 219
220 - def __init__(self):
221 if not flask.request.json: 222 return 223 self.tmp = tempfile.mkdtemp(dir=app.config["STORAGE_DIR"]) 224 log.debug("storing hook content under %s", self.tmp) 225 try: 226 with open(os.path.join(self.tmp, 'hook_payload'), "w") as f: 227 # Do we need to dump http headers, too? 228 f.write(flask.request.data.decode('ascii')) 229 230 except Exception: 231 log.exception('can not store hook payload') 232 self.delete()
233
234 - def rebuild_dict(self):
235 if self.tmp: 236 return {'tmp': os.path.basename(self.tmp), 'hook_data': True } 237 return {}
238
239 - def delete(self):
240 if self.tmp: 241 shutil.rmtree(self.tmp)
242
243 244 @webhooks_ns.route("/custom/<int:copr_id>/<uuid>/", methods=["POST"]) 245 @webhooks_ns.route("/custom/<int:copr_id>/<uuid>/<package_name>/", methods=["POST"]) 246 @copr_id_and_uuid_required 247 @package_name_required 248 @skip_invalid_calls 249 -def webhooks_package_custom(copr, package, flavor=None):
250 # Each source provider (github, gitlab, pagure, ...) provides different 251 # "payload" format for different events. Parsing it here is burden we can 252 # do one day, but now just dump the hook contents somewhere so users can 253 # parse manually. 254 storage = HookContentStorage() 255 try: 256 build = BuildsLogic.rebuild_package(package, storage.rebuild_dict()) 257 db.session.commit() 258 except Exception: 259 log.exception('can not submit build from webhook') 260 storage.delete() 261 return "BUILD_REQUEST_ERROR\n", 500 262 263 # Return the build ID, so (e.g.) the CI process (e.g. Travis job) knows 264 # what build results to wait for. 265 return str(build.id) + "\n", 200
266