34
34
logging .basicConfig (level = logging .INFO )
35
35
logger = logging .getLogger (__name__ )
36
36
37
- def gh_request (method_name , url , body = None ):
37
+ def gh_request (method_name , url , body = None , media_type = None ):
38
38
github_token = os .environ .get ('GITHUB_TOKEN' )
39
39
40
40
kwargs = {
41
41
'headers' : {
42
42
'Authorization' : 'token {}' .format (github_token ),
43
- 'Accept' : 'application/vnd.github.v3+json'
43
+ 'Accept' : media_type or 'application/vnd.github.v3+json'
44
44
}
45
45
}
46
46
method = getattr (requests , method_name .lower ())
@@ -145,51 +145,71 @@ def update_ref(self, refspec, revision):
145
145
gh_request ('PATCH' , url , { 'sha' : revision })
146
146
147
147
@guard ('core' )
148
- def create_deployment (self , pull_request ):
148
+ def create_deployment (self , pull_request , revision ):
149
149
url = '{}/repos/{}/deployments' .format (
150
150
self ._host , self ._github_project
151
151
)
152
- ref = 'refs/pull/{number}/head' .format (** pull_request )
152
+ # The pull request preview system only exposes one deployment for a
153
+ # given pull request. Identifying the deployment by the pull request
154
+ # number ensures that GitHub.com automatically responds to new
155
+ # deployments by designating prior deployments as "inactive"
156
+ environment = 'gh-{}' .format (pull_request ['number' ])
153
157
154
158
logger .info ('Creating deployment for "%s"' , ref )
155
159
156
- gh_request ('POST' , url , {
157
- 'ref' : ref ,
158
- # The pull request preview system only exposes one deployment for
159
- # a given pull request. Identifying the deployment by the pull
160
- # request number ensures that GitHub.com automatically responds to
161
- # new deployments by designating prior deployments as "inactive"
162
- 'environment' : str (pull_request ['number' ]),
160
+ return gh_request ('POST' , url , {
161
+ 'ref' : revision ,
162
+ 'environment' : environment ,
163
+ 'auto_merge' : False ,
163
164
# Pull request previews are created regardless of GitHub Commit
164
165
# Status Checks, so Status Checks should be ignored when creating
165
166
# GitHub Deployments.
166
167
'required_contexts' : []
167
- })
168
+ }, 'application/vnd.github.ant-man-preview+json' )
168
169
169
170
@guard ('core' )
170
- def get_deployment (self , pull_request ):
171
- url = '{}/repos/{}/deployments?environment ={}' .format (
172
- self ._host , self ._github_project , pull_request [ 'number' ]
171
+ def get_deployment (self , revision ):
172
+ url = '{}/repos/{}/deployments?sha ={}' .format (
173
+ self ._host , self ._github_project , revision
173
174
)
174
175
175
176
deployments = gh_request ('GET' , url )
176
177
177
178
return deployments .pop () if len (deployments ) else None
178
179
179
180
@guard ('core' )
180
- def update_deployment (self , deployment , state , description = '' ):
181
+ def deployment_is_pending (self , deployment ):
181
182
url = '{}/repos/{}/deployments/{}/statuses' .format (
182
183
self ._host , self ._github_project , deployment ['id' ]
183
184
)
184
- environment_url = '{}/submissions/{}/' .format (
185
- target , deployment ['environment' ]
185
+
186
+ statuses = sorted (
187
+ gh_request ('GET' , url ),
188
+ key = lambda status : status ['created_at' ]
189
+ )
190
+
191
+ if len (statuses ) == 0 :
192
+ return False
193
+
194
+ return statuses [- 1 ]['state' ] == 'pending'
195
+
196
+ @guard ('core' )
197
+ def update_deployment (self , target , deployment , state , description = '' ):
198
+ if state in ('pending' , 'success' ):
199
+ environment_url = '{}/submissions/{}' .format (
200
+ target , deployment ['environment' ]
201
+ )
202
+ else :
203
+ environment_url = None
204
+ url = '{}/repos/{}/deployments/{}/statuses' .format (
205
+ self ._host , self ._github_project , deployment ['id' ]
186
206
)
187
207
188
208
gh_request ('POST' , url , {
189
209
'state' : state ,
190
210
'description' : description ,
191
211
'environment_url' : environment_url
192
- })
212
+ }, 'application/vnd.github.ant-man-preview+json' )
193
213
194
214
class Remote (object ):
195
215
def __init__ (self , name ):
@@ -243,21 +263,24 @@ def is_deployed(host, deployment):
243
263
244
264
return response .text .strip () == deployment ['sha' ]
245
265
246
- def synchronize (host , github_project , remote_name , window ):
266
+ def synchronize (host , github_project , target , remote_name , window ):
247
267
'''Inspect all pull requests which have been modified in a given window of
248
268
time. Add or remove the "preview" label and update or delete the relevant
249
269
git refs according to the status of each pull request.'''
250
270
251
271
project = Project (host , github_project )
252
272
remote = Remote (remote_name )
273
+
253
274
pull_requests = project .get_pull_requests (
254
275
time .gmtime (time .time () - window )
255
276
)
256
277
257
278
for pull_request in pull_requests :
258
279
logger .info ('Processing pull request #%(number)d' , pull_request )
259
280
260
- refspec_labeled = 'prs-labeled-for-preview/{number}' .format (** pull_request )
281
+ refspec_labeled = 'prs-labeled-for-preview/{number}' .format (
282
+ ** pull_request
283
+ )
261
284
refspec_open = 'prs-open/{number}' .format (** pull_request )
262
285
revision_latest = remote .get_revision (
263
286
'pull/{number}/head' .format (** pull_request )
@@ -281,8 +304,14 @@ def synchronize(host, github_project, remote_name, window):
281
304
elif revision_open != revision_latest :
282
305
project .update_ref (refspec_open , revision_latest )
283
306
284
- if project .get_deployment (pull_request ) is None :
285
- project .create_deployment (pull_request )
307
+ deployment = project .get_deployment (revision_latest )
308
+ if deployment is None :
309
+ deployment = project .create_deployment (
310
+ pull_request , revision_latest
311
+ )
312
+
313
+ if not project .deployment_is_pending (deployment ):
314
+ project .update_deployment (target , deployment , 'pending' )
286
315
else :
287
316
logger .info ('Pull request should not be mirrored' )
288
317
@@ -304,16 +333,20 @@ def detect(host, github_project, target, timeout):
304
333
305
334
logger .info ('Event data: %s' , json .dumps (data , indent = 2 ))
306
335
307
- deployment = data ['deployment' ]
336
+ if data ['deployment_status' ]['state' ] != 'pending' :
337
+ logger .info ('Deployment is not pending. Exiting.' )
338
+ return
308
339
309
- pr_number = int ( deployment [ 'environment' ])
340
+ deployment = data [ 'deployment' ]
310
341
311
- project .update_deployment (deployment , 'in_progress' )
342
+ if not deployment ['environment' ].startswith ('gh-' ):
343
+ logger .info ('Deployment environment is unrecognized. Exiting.' )
344
+ return
312
345
313
346
logger .info (
314
- 'Waiting up to %d seconds for pull request #%d to be deployed to %s' ,
347
+ 'Waiting up to %d seconds for deployment %s to be available on %s' ,
315
348
timeout ,
316
- pr_number ,
349
+ deployment [ 'environment' ] ,
317
350
target
318
351
)
319
352
@@ -322,12 +355,12 @@ def detect(host, github_project, target, timeout):
322
355
while not is_deployed (target , deployment ):
323
356
if time .time () - start > timeout :
324
357
message = 'Deployment did not become available after {} seconds' .format (timeout )
325
- project .update_deployment (deployment , 'error' , message )
358
+ project .update_deployment (target , deployment , 'error' , message )
326
359
raise Exception (message )
327
360
328
361
time .sleep (POLLING_PERIOD )
329
362
330
- project .update_deployment (deployment , 'success' )
363
+ project .update_deployment (target , deployment , 'success' )
331
364
332
365
if __name__ == '__main__' :
333
366
parser = argparse .ArgumentParser ()
@@ -339,6 +372,7 @@ def detect(host, github_project, target, timeout):
339
372
help = '''the GitHub organization and GitHub project name, separated by
340
373
a forward slash (e.g. "web-platform-tests/wpt")'''
341
374
)
375
+ parser .add_argument ('--target' , required = True )
342
376
subparsers = parser .add_subparsers (title = 'subcommands' )
343
377
344
378
parser_sync = subparsers .add_parser (
@@ -349,7 +383,6 @@ def detect(host, github_project, target, timeout):
349
383
parser_sync .set_defaults (func = synchronize )
350
384
351
385
parser_detect = subparsers .add_parser ('detect' , help = detect .__doc__ )
352
- parser_detect .add_argument ('--target' , required = True )
353
386
parser_detect .add_argument ('--timeout' , type = int , required = True )
354
387
parser_detect .set_defaults (func = detect )
355
388
0 commit comments