not-lain commited on
Commit
c34b633
1 Parent(s): 9d51341

refactor code and fix readme title

Browse files
Files changed (1) hide show
  1. src/gradio_space_ci/webhook.py +58 -89
src/gradio_space_ci/webhook.py CHANGED
@@ -207,13 +207,13 @@ def recover_after_restart(space_id: str) -> None:
207
  if not is_pr_synced(space_id=space_id, pr_num=discussion.num):
208
  # Found a PR that is not yet synced
209
  print(f"Recovery. Found an open PR that is not synced: {discussion.url}. Syncing it.")
210
- background_pool.submit(sync_ci_space, space_id=space_id, pr_num=discussion.num)
211
  if discussion.status == "merged" or discussion.status == "closed":
212
  ci_space_id = _get_ci_space_id(space_id=space_id, pr_num=discussion.num)
213
  if repo_exists(repo_id=ci_space_id, repo_type="space"):
214
  # Found a PR for which the CI space has not been deleted
215
  print(f"Recovery. Found a closed PR with an active CI space: {discussion.url}. Deleting it.")
216
- background_pool.submit(delete_ci_space, space_id=space_id, pr_num=discussion.num)
217
 
218
 
219
  ###
@@ -258,13 +258,13 @@ async def trigger_ci_on_pr(payload: WebhookPayload, task_queue: BackgroundTasks)
258
  and payload.discussion.isPullRequest
259
  and payload.discussion.status in ACTIVE_PR_STATUS
260
  ):
261
- # A comment, is it by a command ?
262
  if payload.event.scope == "discussion.comment":
263
  handle_command(space_id=space_id, payload=payload)
264
  # Always sync (in case the space was sleeping or building)
265
  if not is_pr_synced(space_id=space_id, pr_num=payload.discussion.num):
266
  # New PR! Sync task scheduled
267
- task_queue.add_task(sync_ci_space, space_id=space_id, pr_num=payload.discussion.num)
268
  has_task = True
269
  elif (
270
  # Means "a PR has been merged or closed"
@@ -274,11 +274,7 @@ async def trigger_ci_on_pr(payload: WebhookPayload, task_queue: BackgroundTasks)
274
  and payload.discussion.isPullRequest
275
  and (payload.discussion.status == "merged" or payload.discussion.status == "closed")
276
  ):
277
- task_queue.add_task(
278
- delete_ci_space,
279
- space_id=space_id,
280
- pr_num=payload.discussion.num,
281
- )
282
  has_task = True
283
  elif (
284
  # Means "some content has been pushed to the Space" (any branch)
@@ -290,7 +286,7 @@ async def trigger_ci_on_pr(payload: WebhookPayload, task_queue: BackgroundTasks)
290
  if discussion.is_pull_request and discussion.status in ACTIVE_PR_STATUS:
291
  if not is_pr_synced(space_id=space_id, pr_num=discussion.num):
292
  # Found a PR that is not yet synced
293
- task_queue.add_task(sync_ci_space, space_id=space_id, pr_num=discussion.num)
294
  has_task = True
295
 
296
  if has_task:
@@ -321,9 +317,9 @@ def is_pr_synced(space_id: str, pr_num: int) -> bool:
321
  return last_synced_sha == last_pr_sha
322
 
323
 
324
- def sync_ci_space(space_id: str, pr_num: int) -> None:
325
  print(f"New task: sync ephemeral env for {space_id} (PR {pr_num})")
326
- if is_pr_synced(space_id=space_id, pr_num=pr_num):
327
  print("Already synced. Nothing to do.")
328
  return
329
 
@@ -334,11 +330,13 @@ def sync_ci_space(space_id: str, pr_num: int) -> None:
334
 
335
  # Configure ephemeral Space if trusted author
336
  is_configured = False
337
- if is_new:
338
  is_configured = configure_ephemeral_space(space_id=space_id, pr_num=pr_num)
339
 
340
  # Download space codebase from PR revision
341
- snapshot_path = Path(snapshot_download(repo_id=space_id, revision=f"refs/pr/{pr_num}", repo_type="space"))
 
 
342
 
343
  # Overwrite README file in cache (/!\)
344
  readme_path = snapshot_path / "README.md"
@@ -360,7 +358,9 @@ def sync_ci_space(space_id: str, pr_num: int) -> None:
360
  readme_path.unlink(missing_ok=True)
361
 
362
  # Post a comment on the PR
363
- if is_new and is_configured:
 
 
364
  notify_pr(space_id=space_id, pr_num=pr_num, action="created_and_configured")
365
  elif is_new:
366
  notify_pr(space_id=space_id, pr_num=pr_num, action="created_not_configured")
@@ -390,7 +390,14 @@ def create_ephemeral_space(space_id: str, pr_num: int) -> bool:
390
  raise
391
 
392
 
393
- def configure_ephemeral_space(space_id: str, pr_num: int) -> bool:
 
 
 
 
 
 
 
394
  # Config values
395
  ci_space_id = _get_ci_space_id(space_id=space_id, pr_num=pr_num)
396
  trusted_authors: List[str] = EPHEMERAL_SPACES_CONFIG["trusted_authors"]
@@ -401,25 +408,25 @@ def configure_ephemeral_space(space_id: str, pr_num: int) -> bool:
401
 
402
  # Check if trusted author
403
  details = get_discussion_details(repo_id=space_id, repo_type="space", discussion_num=pr_num)
404
- if details.author not in trusted_authors:
405
- return False # not a trusted author => do NOT set secrets, hardware, storage, etc.
406
-
407
- # Configure space
408
- for key, value in variables.items():
409
- add_space_variable(ci_space_id, key, value)
410
- for key, value in secrets.items():
411
- add_space_secret(ci_space_id, key, value)
412
-
413
- # Request hardware/storage for space
414
- if hardware is not None and hardware != SpaceHardware.CPU_BASIC:
415
- request_space_hardware(ci_space_id, hardware, sleep_time=5 * 60) # sleep after 5min on PR Spaces with GPU
416
- if storage is not None:
417
- request_space_storage(ci_space_id, storage)
418
 
419
- return True
 
 
420
 
421
 
422
- def delete_ci_space(space_id: str, pr_num: int) -> None:
423
  print(f"New task: delete ephemeral env for {space_id} (PR {pr_num})")
424
 
425
  # Delete
@@ -431,7 +438,9 @@ def delete_ci_space(space_id: str, pr_num: int) -> None:
431
  return
432
 
433
  # Notify about deletion
434
- notify_pr(space_id=space_id, pr_num=pr_num, action="deleted")
 
 
435
 
436
 
437
  def notify_pr(
@@ -451,9 +460,9 @@ def notify_pr(
451
  elif action == "deleted":
452
  comment = NOTIFICATION_TEMPLATE_DELETED
453
  elif action == "trusted_pr":
454
- comment = NOTIFICATION_TEMPLATE_TRUSTED_PR
455
  elif action == "untrusted_pr":
456
- comment = NOTIFICATION_TEMPLATE_UNTRUSTED_PR
457
  else:
458
  raise ValueError(f"Status {action} not handled.")
459
 
@@ -464,57 +473,13 @@ def _get_ci_space_id(space_id: str, pr_num: int) -> str:
464
  return f"{space_id}-ci-pr-{pr_num}"
465
 
466
 
467
- def set_config(space_id: str, pr_num: str) -> None:
468
- """a function to set the ephemerial space config"""
469
- variables: Dict[str, str] = EPHEMERAL_SPACES_CONFIG["variables"]
470
- secrets: Dict[str, str] = EPHEMERAL_SPACES_CONFIG["secrets"]
471
- hardware: Optional[SpaceHardware] = EPHEMERAL_SPACES_CONFIG["hardware"]
472
- storage: Optional[SpaceHardware] = EPHEMERAL_SPACES_CONFIG["storage"]
473
- ci_space_id = _get_ci_space_id(space_id=space_id, pr_num=pr_num)
474
- # Configure space
475
- for key, value in variables.items():
476
- add_space_variable(ci_space_id, key, value)
477
- for key, value in secrets.items():
478
- add_space_secret(ci_space_id, key, value)
479
-
480
- # Request hardware/storage for space
481
- if hardware is not None and hardware != SpaceHardware.CPU_BASIC:
482
- request_space_hardware(ci_space_id, hardware, sleep_time=5 * 60) # sleep after 5min on PR Spaces with GPU
483
- if storage is not None:
484
- request_space_storage(ci_space_id, storage)
485
-
486
-
487
  def rebuild_space(space_id: str, pr_num: int) -> None:
488
  "a function to rebuild the ephemeral space without config"
489
  # This is useful to cut down on resource usage and to remove tokens from
490
  # the ephemeral space
491
- ci_space_id = _get_ci_space_id(space_id=space_id, pr_num=pr_num)
492
- try:
493
- delete_repo(repo_id=ci_space_id, repo_type="space")
494
- except RepositoryNotFoundError:
495
- pass
496
- create_ephemeral_space(space_id=space_id, pr_num=pr_num)
497
- # Download space codebase from PR revision
498
- snapshot_path = Path(snapshot_download(repo_id=space_id, revision=f"refs/pr/{pr_num}", repo_type="space"))
499
-
500
- # Overwrite README file in cache (/!\)
501
- readme_path = snapshot_path / "README.md"
502
- card = RepoCard.load(readme_path)
503
- setattr(card.data, "synced_sha", snapshot_path.name) # latest sha
504
- card.data.title = f"{card.data.title} (ephemeral #{pr_num})"
505
- card.save(readme_path)
506
-
507
- # Sync space codebase with PR revision
508
- upload_folder(
509
- repo_id=ci_space_id,
510
- repo_type="space",
511
- commit_message=f"Sync CI Space with PR {pr_num}.",
512
- folder_path=snapshot_path,
513
- delete_patterns="*",
514
- )
515
-
516
- # Delete readme file from cache (just in case)
517
- readme_path.unlink(missing_ok=True)
518
 
519
 
520
  def handle_modification(space_id: str, discussion: Any) -> None:
@@ -522,10 +487,13 @@ def handle_modification(space_id: str, discussion: Any) -> None:
522
  if not repo_exists(ci_space_id):
523
  return
524
  details = get_discussion_details(repo_id=space_id, repo_type="space", discussion_num=discussion.num)
525
- event_author = details.events[-1]._event["author"]["name"] # username of that event
526
- if event_author not in EPHEMERAL_SPACES_CONFIG["trusted_authors"]:
527
- # Untrusted author, we rebuild the space
528
- rebuild_space(space_id=space_id, pr_num=discussion.num)
 
 
 
529
 
530
 
531
  def handle_command(space_id: str, payload: WebhookPayload) -> None:
@@ -535,11 +503,10 @@ def handle_command(space_id: str, payload: WebhookPayload) -> None:
535
  event_author = details.events[-1]._event["author"]["name"] # username of that event
536
  if event_author in EPHEMERAL_SPACES_CONFIG["trusted_authors"]:
537
  if payload.comment.content == "/trust_pr":
538
- set_config(space_id=space_id, pr_num=pr_num)
539
  notify_pr(space_id=space_id, pr_num=pr_num, action="trusted_pr")
540
  elif payload.comment.content == "/untrust_pr":
541
  rebuild_space(space_id=space_id, pr_num=pr_num)
542
- notify_pr(space_id=space_id, pr_num=pr_num, action="untrusted_pr")
543
 
544
 
545
  NOTIFICATION_TEMPLATE_CREATED_AND_CONFIGURED = """\
@@ -566,12 +533,14 @@ _(This is an automated message.)_
566
 
567
  NOTIFICATION_TEMPLATE_TRUSTED_PR = """\
568
  This PR has been granted temporary trust status Thus granting it with the appropriate approriate hardware, storage, and secrets.
569
- Trust status will be revokeduser either when a trusted author uses `/untrust_pr` command or when new commits are pushed to this PR.
 
570
  _(This is an automated message.)_
571
  """
572
 
573
  NOTIFICATION_TEMPLATE_UNTRUSTED_PR = """\
574
  This PR has been untrusted. Thus resetting all hardware, storage, and secrets.
 
575
  _(This is an automated message.)_
576
  """
577
 
 
207
  if not is_pr_synced(space_id=space_id, pr_num=discussion.num):
208
  # Found a PR that is not yet synced
209
  print(f"Recovery. Found an open PR that is not synced: {discussion.url}. Syncing it.")
210
+ background_pool.submit(sync_ci_space, space_id=space_id, pr_num=discussion.num, skip_config=False)
211
  if discussion.status == "merged" or discussion.status == "closed":
212
  ci_space_id = _get_ci_space_id(space_id=space_id, pr_num=discussion.num)
213
  if repo_exists(repo_id=ci_space_id, repo_type="space"):
214
  # Found a PR for which the CI space has not been deleted
215
  print(f"Recovery. Found a closed PR with an active CI space: {discussion.url}. Deleting it.")
216
+ background_pool.submit(delete_ci_space, space_id=space_id, pr_num=discussion.num, notify=True)
217
 
218
 
219
  ###
 
258
  and payload.discussion.isPullRequest
259
  and payload.discussion.status in ACTIVE_PR_STATUS
260
  ):
261
+ # A comment, is it a command ?
262
  if payload.event.scope == "discussion.comment":
263
  handle_command(space_id=space_id, payload=payload)
264
  # Always sync (in case the space was sleeping or building)
265
  if not is_pr_synced(space_id=space_id, pr_num=payload.discussion.num):
266
  # New PR! Sync task scheduled
267
+ task_queue.add_task(sync_ci_space, space_id=space_id, pr_num=payload.discussion.num, skip_config=False)
268
  has_task = True
269
  elif (
270
  # Means "a PR has been merged or closed"
 
274
  and payload.discussion.isPullRequest
275
  and (payload.discussion.status == "merged" or payload.discussion.status == "closed")
276
  ):
277
+ task_queue.add_task(delete_ci_space, space_id=space_id, pr_num=payload.discussion.num, notify=True)
 
 
 
 
278
  has_task = True
279
  elif (
280
  # Means "some content has been pushed to the Space" (any branch)
 
286
  if discussion.is_pull_request and discussion.status in ACTIVE_PR_STATUS:
287
  if not is_pr_synced(space_id=space_id, pr_num=discussion.num):
288
  # Found a PR that is not yet synced
289
+ task_queue.add_task(sync_ci_space, space_id=space_id, pr_num=discussion.num, skip_config=False)
290
  has_task = True
291
 
292
  if has_task:
 
317
  return last_synced_sha == last_pr_sha
318
 
319
 
320
+ def sync_ci_space(space_id: str, pr_num: int, skip_config: bool = False) -> None:
321
  print(f"New task: sync ephemeral env for {space_id} (PR {pr_num})")
322
+ if is_pr_synced(space_id=space_id, pr_num=pr_num) and not skip_config:
323
  print("Already synced. Nothing to do.")
324
  return
325
 
 
330
 
331
  # Configure ephemeral Space if trusted author
332
  is_configured = False
333
+ if not skip_config and is_new:
334
  is_configured = configure_ephemeral_space(space_id=space_id, pr_num=pr_num)
335
 
336
  # Download space codebase from PR revision
337
+ snapshot_path = Path(
338
+ snapshot_download(repo_id=space_id, revision=f"refs/pr/{pr_num}", repo_type="space", force_download=True)
339
+ )
340
 
341
  # Overwrite README file in cache (/!\)
342
  readme_path = snapshot_path / "README.md"
 
358
  readme_path.unlink(missing_ok=True)
359
 
360
  # Post a comment on the PR
361
+ if is_new and skip_config:
362
+ notify_pr(space_id=space_id, pr_num=pr_num, action="untrusted_pr")
363
+ elif is_new and is_configured:
364
  notify_pr(space_id=space_id, pr_num=pr_num, action="created_and_configured")
365
  elif is_new:
366
  notify_pr(space_id=space_id, pr_num=pr_num, action="created_not_configured")
 
390
  raise
391
 
392
 
393
+ ##
394
+ # configure_ephemeral_space logic
395
+ # if a pr is made by a trusted author or an author trusts a pr
396
+ # => we configure the ephemeral space
397
+ ##
398
+
399
+
400
+ def configure_ephemeral_space(space_id: str, pr_num: int, trusted_pr=False) -> bool:
401
  # Config values
402
  ci_space_id = _get_ci_space_id(space_id=space_id, pr_num=pr_num)
403
  trusted_authors: List[str] = EPHEMERAL_SPACES_CONFIG["trusted_authors"]
 
408
 
409
  # Check if trusted author
410
  details = get_discussion_details(repo_id=space_id, repo_type="space", discussion_num=pr_num)
411
+ if details.author in trusted_authors or trusted_pr is True:
412
+ # Configure space
413
+ for key, value in variables.items():
414
+ add_space_variable(ci_space_id, key, value)
415
+ for key, value in secrets.items():
416
+ add_space_secret(ci_space_id, key, value)
417
+
418
+ # Request hardware/storage for space
419
+ if hardware is not None and hardware != SpaceHardware.CPU_BASIC:
420
+ request_space_hardware(ci_space_id, hardware, sleep_time=5 * 60) # sleep after 5min on PR Spaces with GPU
421
+ if storage is not None:
422
+ request_space_storage(ci_space_id, storage)
 
 
423
 
424
+ return True
425
+ else:
426
+ return False
427
 
428
 
429
+ def delete_ci_space(space_id: str, pr_num: int, notify: bool = True) -> None:
430
  print(f"New task: delete ephemeral env for {space_id} (PR {pr_num})")
431
 
432
  # Delete
 
438
  return
439
 
440
  # Notify about deletion
441
+ if notify is True:
442
+ # This logic is adaped across multiple functions so we will not always notify the pr
443
+ notify_pr(space_id=space_id, pr_num=pr_num, action="deleted")
444
 
445
 
446
  def notify_pr(
 
460
  elif action == "deleted":
461
  comment = NOTIFICATION_TEMPLATE_DELETED
462
  elif action == "trusted_pr":
463
+ comment = NOTIFICATION_TEMPLATE_TRUSTED_PR.format(ci_space_id=ci_space_id)
464
  elif action == "untrusted_pr":
465
+ comment = NOTIFICATION_TEMPLATE_UNTRUSTED_PR.format(ci_space_id=ci_space_id)
466
  else:
467
  raise ValueError(f"Status {action} not handled.")
468
 
 
473
  return f"{space_id}-ci-pr-{pr_num}"
474
 
475
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
476
  def rebuild_space(space_id: str, pr_num: int) -> None:
477
  "a function to rebuild the ephemeral space without config"
478
  # This is useful to cut down on resource usage and to remove tokens from
479
  # the ephemeral space
480
+ delete_ci_space(space_id=space_id, pr_num=pr_num, notify=False)
481
+ # create a new synced ephemeral space
482
+ sync_ci_space(space_id=space_id, pr_num=pr_num, skip_config=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
483
 
484
 
485
  def handle_modification(space_id: str, discussion: Any) -> None:
 
487
  if not repo_exists(ci_space_id):
488
  return
489
  details = get_discussion_details(repo_id=space_id, repo_type="space", discussion_num=discussion.num)
490
+ # If last commit is not by a trusted author we rebuild it
491
+ for event in details.events[::-1]:
492
+ if event.type == "commit":
493
+ if event._event["author"]["name"] not in EPHEMERAL_SPACES_CONFIG["trusted_authors"]:
494
+ rebuild_space(space_id=space_id, pr_num=discussion.num)
495
+ else:
496
+ return
497
 
498
 
499
  def handle_command(space_id: str, payload: WebhookPayload) -> None:
 
503
  event_author = details.events[-1]._event["author"]["name"] # username of that event
504
  if event_author in EPHEMERAL_SPACES_CONFIG["trusted_authors"]:
505
  if payload.comment.content == "/trust_pr":
506
+ configure_ephemeral_space(space_id=space_id, pr_num=pr_num, trusted_pr=True)
507
  notify_pr(space_id=space_id, pr_num=pr_num, action="trusted_pr")
508
  elif payload.comment.content == "/untrust_pr":
509
  rebuild_space(space_id=space_id, pr_num=pr_num)
 
510
 
511
 
512
  NOTIFICATION_TEMPLATE_CREATED_AND_CONFIGURED = """\
 
533
 
534
  NOTIFICATION_TEMPLATE_TRUSTED_PR = """\
535
  This PR has been granted temporary trust status Thus granting it with the appropriate approriate hardware, storage, and secrets.
536
+ You can access the ephemeral Space at [{ci_space_id}](https://huggingface.co/spaces/{ci_space_id}).
537
+ Trust status will be revoked either when a trusted author uses `/untrust_pr` command or when new commits are pushed to this PR.
538
  _(This is an automated message.)_
539
  """
540
 
541
  NOTIFICATION_TEMPLATE_UNTRUSTED_PR = """\
542
  This PR has been untrusted. Thus resetting all hardware, storage, and secrets.
543
+ You can access this PR's ephemeral Space at [{ci_space_id}](https://huggingface.co/spaces/{ci_space_id}).
544
  _(This is an automated message.)_
545
  """
546