diff --git a/src/hubcast/clients/github/client.py b/src/hubcast/clients/github/client.py index 086bd65f..1ee38fc3 100644 --- a/src/hubcast/clients/github/client.py +++ b/src/hubcast/clients/github/client.py @@ -1,10 +1,14 @@ +import logging from urllib.parse import urlparse import aiohttp +from gidgethub import HTTPException from gidgethub import aiohttp as gh_aiohttp from .auth import GitHubAuthenticator +log = logging.getLogger(__name__) + GH_REACTIONS = { "+1": "THUMBS_UP", "-1": "THUMBS_DOWN", @@ -127,7 +131,18 @@ async def get_repo_config(self): # get the contents of the repository hubcast.yml file url = f"/repos/{self.repo_owner}/{self.repo_name}/contents/.github/hubcast.yml" # get raw contents rather than base64 encoded text - return await gh.getitem(url, accept="application/vnd.github.raw") + try: + return await gh.getitem(url, accept="application/vnd.github.raw") + except HTTPException as exc: + if exc.status_code == 404: + log.info( + "Repo config file not found at .github/hubcast.yml", + extra={ + "repo_owner": self.repo_owner, + "repo_name": self.repo_name, + }, + ) + raise async def get_pr(self, id): """Return individual PR data.""" diff --git a/src/hubcast/clients/gitlab/client.py b/src/hubcast/clients/gitlab/client.py index 41999eb7..8c4ea27a 100644 --- a/src/hubcast/clients/gitlab/client.py +++ b/src/hubcast/clients/gitlab/client.py @@ -1,11 +1,14 @@ +import logging import urllib.parse from typing import Dict import aiohttp -import gidgetlab.aiohttp +import gidgetlab from .auth import GitLabAuthenticator, GitLabSingleUserAuthenticator +log = logging.getLogger(__name__) + class GitLabClientFactory: def __init__( @@ -84,8 +87,15 @@ async def set_webhook(self, gl_fullname: str, data: Dict): repo_id = urllib.parse.quote_plus(gl_fullname) url = f"/projects/{repo_id}/hooks" - - hooks_data = await gl.getitem(url) + try: + hooks_data = await gl.getitem(url) + except gidgetlab.exceptions.BadRequest as exc: + if exc.status_code == 403: + log.info( + "User cannot access GitLab webhooks; skipping set_webhook. Must have `maintainer` role.", + extra={"user": self.user, "repo": gl_fullname}, + ) + return for hook in hooks_data: if hook["name"] == "hubcast": existing_hook = hook diff --git a/src/hubcast/web/github/routes.py b/src/hubcast/web/github/routes.py index 46ba800c..e40a9e62 100644 --- a/src/hubcast/web/github/routes.py +++ b/src/hubcast/web/github/routes.py @@ -53,18 +53,25 @@ async def sync_branch(event, gh, gl, gl_user, *arg, **kwargs): if await gh.get_prs(branch=target_ref): return - repo_config = await get_repo_config(gh, src_fullname, refresh=True) + # only refresh config on default branch pushes (where .github/hubcast.yml lives) + default_branch = event.data["repository"]["default_branch"] + is_default_branch = target_ref == f"refs/heads/{default_branch}" + repo_config, fetched = await get_repo_config( + gh, src_fullname, refresh=is_default_branch + ) dest_fullname = f"{repo_config.dest_org}/{repo_config.dest_name}" dest_remote_url = f"{gl.instance_url}/{dest_fullname}.git" - # setup callback webhook on GitLab - webhook_data = { - "gh_owner": src_owner, - "gh_repo": src_repo_name, - "gh_check": repo_config.check_name, - } - await gl.set_webhook(dest_fullname, webhook_data) + # only set/update webhook on default branch pushes when config cache was bypassed (refresh or initial fetch) + if fetched and is_default_branch: + # setup callback webhook on GitLab + webhook_data = { + "gh_owner": src_owner, + "gh_repo": src_repo_name, + "gh_check": repo_config.check_name, + } + await gl.set_webhook(dest_fullname, webhook_data) gl_token = await gl.auth.authenticate_user(gl_user) # sync commits from GitHub -> GitLab @@ -113,7 +120,7 @@ async def remove_branch(event, gh, gl, gl_user, *arg, **kwargs): src_fullname = event.data["repository"]["full_name"] target_ref = event.data["ref"] - repo_config = await get_repo_config(gh, src_fullname, refresh=True) + repo_config, _ = await get_repo_config(gh, src_fullname) dest_fullname = f"{repo_config.dest_org}/{repo_config.dest_name}" dest_remote_url = f"{gl.instance_url}/{dest_fullname}.git" @@ -173,7 +180,7 @@ async def sync_pr(pull_request, gh, gl, gl_user, src_repo_private, want_sha): return # get the repository configuration from .github/hubcast.yml - repo_config = await get_repo_config(gh, base_fullname) + repo_config, _ = await get_repo_config(gh, base_fullname) if not repo_config.draft_sync and pull_request["draft"]: if repo_config.draft_sync_msg: await gh.set_check_status( @@ -274,7 +281,7 @@ async def remove_pr(event, gh, gl, gl_user, *arg, **kwargs): target_ref = f"refs/heads/pr-{pull_request_id}" # get the repository configuration from .github/hubcast.yml - repo_config = await get_repo_config(gh, base_fullname) + repo_config, _ = await get_repo_config(gh, base_fullname) dest_fullname = f"{repo_config.dest_org}/{repo_config.dest_name}" dest_remote_url = f"{gl.instance_url}/{dest_fullname}.git" @@ -352,7 +359,7 @@ async def respond_comment(event, gh, gl, gl_user, *arg, **kwargs): branch = pull_request["head"]["ref"] # get the gitlab repo information and run the pipeline - repo_config = await get_repo_config(gh, base_fullname, refresh=True) + repo_config, _ = await get_repo_config(gh, base_fullname) dest_fullname = f"{repo_config.dest_org}/{repo_config.dest_name}" pipeline_url = await gl.run_pipeline(dest_fullname, branch) @@ -384,7 +391,7 @@ async def respond_comment(event, gh, gl, gl_user, *arg, **kwargs): branch = pull_request["head"]["ref"] # get the gitlab repo information and run the pipeline - repo_config = await get_repo_config(gh, base_fullname, refresh=True) + repo_config, _ = await get_repo_config(gh, base_fullname) dest_fullname = f"{repo_config.dest_org}/{repo_config.dest_name}" pipeline_id = await gl.get_latest_pipeline(dest_fullname, branch) @@ -428,6 +435,6 @@ async def rerun_check(event, gh, gl, gl_user, *arg, **kwargs): return # get the GL repo info and run the pipeline - repo_config = await get_repo_config(gh, src_fullname, refresh=True) + repo_config, _ = await get_repo_config(gh, src_fullname) dest_fullname = f"{repo_config.dest_org}/{repo_config.dest_name}" await gl.run_pipeline(dest_fullname, branch) diff --git a/src/hubcast/web/github/utils.py b/src/hubcast/web/github/utils.py index 6b8c997d..725b5bcc 100644 --- a/src/hubcast/web/github/utils.py +++ b/src/hubcast/web/github/utils.py @@ -21,6 +21,7 @@ def create_config(fullname: str, data: Dict) -> RepoConfig: async def get_repo_config(gh: GitHubClient, fullname: str, refresh: bool = False): + fetched = False if fullname in config_cache and not refresh: config = config_cache[fullname] else: @@ -43,5 +44,6 @@ async def get_repo_config(gh: GitHubClient, fullname: str, refresh: bool = False ) config_cache[fullname] = config + fetched = True - return config + return config, fetched