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 if ref_type == 'tag': 96 committish = ref 97 else: 98 committish = payload['push']['changes'][0]['new']['target']['hash'] 99 except KeyError: 100 return "Bad Request", 400 101 102 packages = PackagesLogic.get_for_webhook_rebuild( 103 copr_id, uuid, clone_url, commits, ref_type, ref 104 ) 105 106 for package in packages: 107 BuildsLogic.rebuild_package(package, {'committish': committish}) 108 109 db.session.commit() 110 111 return "OK", 200
112
113 114 @webhooks_ns.route("/github/<int:copr_id>/<uuid>/", methods=["POST"]) 115 -def webhooks_git_push(copr_id, uuid):
116 if flask.request.headers["X-GitHub-Event"] == "ping": 117 return "OK", 200 118 # For the documentation of the data we receive see: 119 # https://developer.github.com/v3/activity/events/types/#pushevent 120 try: 121 copr = ComplexLogic.get_copr_by_id_safe(copr_id) 122 except ObjectNotFound: 123 return page_not_found("Project does not exist") 124 125 if copr.webhook_secret != uuid: 126 return access_restricted("This webhook is not valid") 127 128 try: 129 payload = flask.request.json 130 clone_url = payload['repository']['clone_url'] 131 commits = [] 132 payload_commits = payload.get('commits', []) 133 for payload_commit in payload_commits: 134 commits.append({ 135 'added': payload_commit['added'], 136 'modified': payload_commit['modified'], 137 'removed': payload_commit['removed'], 138 }) 139 140 ref_type = payload.get('ref_type', '') 141 ref = payload.get('ref', '') 142 except KeyError: 143 return "Bad Request", 400 144 145 packages = PackagesLogic.get_for_webhook_rebuild(copr_id, uuid, clone_url, commits, ref_type, ref) 146 147 committish = (ref if ref_type == 'tag' else payload.get('after', '')) 148 for package in packages: 149 BuildsLogic.rebuild_package(package, {'committish': committish}) 150 151 db.session.commit() 152 153 return "OK", 200
154
155 156 @webhooks_ns.route("/gitlab/<int:copr_id>/<uuid>/", methods=["POST"]) 157 -def webhooks_gitlab_push(copr_id, uuid):
158 # For the documentation of the data we receive see: 159 # https://gitlab.com/help/user/project/integrations/webhooks#events 160 try: 161 copr = ComplexLogic.get_copr_by_id_safe(copr_id) 162 except ObjectNotFound: 163 return page_not_found("Project does not exist") 164 165 if copr.webhook_secret != uuid: 166 return access_restricted("This webhook is not valid") 167 168 try: 169 payload = flask.request.json 170 clone_url = payload['project']['git_http_url'] 171 commits = [] 172 payload_commits = payload.get('commits', []) 173 for payload_commit in payload_commits: 174 commits.append({ 175 'added': payload_commit['added'], 176 'modified': payload_commit['modified'], 177 'removed': payload_commit['removed'], 178 }) 179 if payload['object_kind'] == 'tag_push': 180 ref_type = 'tag' 181 ref = os.path.basename(payload.get('ref', '')) 182 else: 183 ref_type = None 184 ref = payload.get('ref', '') 185 except KeyError: 186 return "Bad Request", 400 187 188 packages = PackagesLogic.get_for_webhook_rebuild(copr_id, uuid, clone_url, commits, ref_type, ref) 189 190 committish = (ref if ref_type == 'tag' else payload.get('after', '')) 191 for package in packages: 192 BuildsLogic.rebuild_package(package, {'committish': committish}) 193 194 db.session.commit() 195 196 return "OK", 200
197
198 199 -class HookContentStorage(object):
200 tmp = None 201
202 - def __init__(self):
203 if not flask.request.json: 204 return 205 self.tmp = tempfile.mkdtemp(dir=app.config["STORAGE_DIR"]) 206 log.debug("storing hook content under %s", self.tmp) 207 try: 208 with open(os.path.join(self.tmp, 'hook_payload'), "w") as f: 209 # Do we need to dump http headers, too? 210 f.write(flask.request.data.decode('ascii')) 211 212 except Exception: 213 log.exception('can not store hook payload') 214 self.delete()
215
216 - def rebuild_dict(self):
217 if self.tmp: 218 return {'tmp': os.path.basename(self.tmp), 'hook_data': True } 219 return {}
220
221 - def delete(self):
222 if self.tmp: 223 shutil.rmtree(self.tmp)
224
225 226 @webhooks_ns.route("/custom/<int:copr_id>/<uuid>/", methods=["POST"]) 227 @webhooks_ns.route("/custom/<int:copr_id>/<uuid>/<package_name>/", methods=["POST"]) 228 @copr_id_and_uuid_required 229 @package_name_required 230 @skip_invalid_calls 231 -def webhooks_package_custom(copr, package, flavor=None):
232 # Each source provider (github, gitlab, pagure, ...) provides different 233 # "payload" format for different events. Parsing it here is burden we can 234 # do one day, but now just dump the hook contents somewhere so users can 235 # parse manually. 236 storage = HookContentStorage() 237 try: 238 build = BuildsLogic.rebuild_package(package, storage.rebuild_dict()) 239 db.session.commit() 240 except Exception: 241 log.exception('can not submit build from webhook') 242 storage.delete() 243 return "BUILD_REQUEST_ERROR\n", 500 244 245 # Return the build ID, so (e.g.) the CI process (e.g. Travis job) knows 246 # what build results to wait for. 247 return str(build.id) + "\n", 200
248