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