From 90b4a06e64b5c755e0d186a6a925f72184821450 Mon Sep 17 00:00:00 2001 From: tituspijean Date: Fri, 3 Sep 2021 18:03:58 +0200 Subject: [PATCH 1/4] Handle github-actions PRs to trigger CI --- run.py | 70 +++++++++++++++++++++++++++++++++++----------------------- 1 file changed, 42 insertions(+), 28 deletions(-) diff --git a/run.py b/run.py index f1ff037..077e34a 100644 --- a/run.py +++ b/run.py @@ -1021,36 +1021,50 @@ async def github(request): # We expect issue comments (issue = also PR in github stuff...) # - *New* comments # - On issue/PRs which are still open - if hook_type != "issue_comment" \ - or hook_infos["action"] != "created" \ - or hook_infos["issue"]["state"] != "open" \ - or "pull_request" not in hook_infos["issue"]: + if hook_type == "issue_comment": + if hook_infos["action"] != "created" \ + or hook_infos["issue"]["state"] != "open" \ + or "pull_request" not in hook_infos["issue"]: + # Nothing to do but success anyway (204 = No content) + abort(204, "Nothing to do") + + # Check the comment contains proper keyword trigger + body = hook_infos["comment"]["body"].strip()[:100].lower() + triggers = ["!testme", "!gogogadgetoci", "By the power of systemd, I invoke The Great App CI to test this Pull Request!"] + if not any(trigger.lower() in body for trigger in triggers): + # Nothing to do but success anyway (204 = No content) + abort(204, "Nothing to do") + + # We only accept this from people which are member of the org + # https://docs.github.com/en/rest/reference/orgs#check-organization-membership-for-a-user + # We need a token an we can't rely on "author_association" because sometimes, users are members in Private, + # which is not represented in the original webhook + async def is_user_in_organization(user): + token = open("./github_bot_token").read().strip() + async with aiohttp.ClientSession(headers={"Authorization": f"token {token}", "Accept": "application/vnd.github.v3+json"}) as session: + resp = await session.get(f"https://api.github.com/orgs/YunoHost-Apps/members/{user}") + return resp.status == 204 + + if not await is_user_in_organization(hook_infos["comment"]["user"]["login"]): + # Unauthorized + abort(403, "Unauthorized") + type = "issue" + + elif hook_type == "pull_request": + if hook_infos["action"] != "opened": # Nothing to do but success anyway (204 = No content) - abort(204, "Nothing to do") - - # Check the comment contains proper keyword trigger - body = hook_infos["comment"]["body"].strip()[:100].lower() - triggers = ["!testme", "!gogogadgetoci", "By the power of systemd, I invoke The Great App CI to test this Pull Request!"] - if not any(trigger.lower() in body for trigger in triggers): - # Nothing to do but success anyway (204 = No content) - abort(204, "Nothing to do") - - # We only accept this from people which are member of the org - # https://docs.github.com/en/rest/reference/orgs#check-organization-membership-for-a-user - # We need a token an we can't rely on "author_association" because sometimes, users are members in Private, - # which is not represented in the original webhook - async def is_user_in_organization(user): - token = open("./github_bot_token").read().strip() - async with aiohttp.ClientSession(headers={"Authorization": f"token {token}", "Accept": "application/vnd.github.v3+json"}) as session: - resp = await session.get(f"https://api.github.com/orgs/YunoHost-Apps/members/{user}") - return resp.status == 204 - - if not await is_user_in_organization(hook_infos["comment"]["user"]["login"]): - # Unauthorized - abort(403, "Unauthorized") + abort(204, "Nothing to do") + # We only accept PRs that are created by github-action bot + if hook_infos["pull_request"]["user"]["login"] != "github-actions[bot]": + # Unauthorized + abort(403, "Unauthorized") + type = "pull_request" + else: + # Nothing to do but success anyway (204 = No content) + abort(204, "Nothing to do") # Fetch the PR infos (yeah they ain't in the initial infos we get @_@) - pr_infos_url = hook_infos["issue"]["pull_request"]["url"] + pr_infos_url = hook_infos[type]["url"] async with aiohttp.ClientSession() as session: async with session.get(pr_infos_url) as resp: pr_infos = await resp.json() @@ -1076,7 +1090,7 @@ async def github(request): async def comment(body): - comments_url = hook_infos["issue"]["comments_url"] + comments_url = hook_infos[type]["comments_url"] token = open("./github_bot_token").read().strip() async with aiohttp.ClientSession(headers={"Authorization": f"token {token}"}) as session: From 84c6eb1a2adfc84c22ecffc163227999e54d6b0c Mon Sep 17 00:00:00 2001 From: tituspijean Date: Fri, 3 Sep 2021 19:08:22 +0200 Subject: [PATCH 2/4] Fix pr_infos_url retrieval --- run.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/run.py b/run.py index 077e34a..09f2848 100644 --- a/run.py +++ b/run.py @@ -1048,7 +1048,8 @@ async def github(request): if not await is_user_in_organization(hook_infos["comment"]["user"]["login"]): # Unauthorized abort(403, "Unauthorized") - type = "issue" + # Fetch the PR infos (yeah they ain't in the initial infos we get @_@) + pr_infos_url = hook_infos["issue"]["pull_request"]["url"] elif hook_type == "pull_request": if hook_infos["action"] != "opened": @@ -1058,13 +1059,13 @@ async def github(request): if hook_infos["pull_request"]["user"]["login"] != "github-actions[bot]": # Unauthorized abort(403, "Unauthorized") - type = "pull_request" + # Fetch the PR infos (yeah they ain't in the initial infos we get @_@) + pr_infos_url = hook_infos["pull_request"]["url"] + else: # Nothing to do but success anyway (204 = No content) abort(204, "Nothing to do") - # Fetch the PR infos (yeah they ain't in the initial infos we get @_@) - pr_infos_url = hook_infos[type]["url"] async with aiohttp.ClientSession() as session: async with session.get(pr_infos_url) as resp: pr_infos = await resp.json() @@ -1089,8 +1090,10 @@ async def github(request): # Answer with comment with link+badge for the job async def comment(body): - - comments_url = hook_infos[type]["comments_url"] + if hook_type == "issue_comment": + comments_url = hook_infos["issue"]["comments_url"] + else: + comments_url = hook_infos["pull_request"]["comments_url"] token = open("./github_bot_token").read().strip() async with aiohttp.ClientSession(headers={"Authorization": f"token {token}"}) as session: From 6c15f5aba75433fda3b5f841d744c3b2b50b4b38 Mon Sep 17 00:00:00 2001 From: tituspijean Date: Sat, 4 Sep 2021 00:13:01 +0200 Subject: [PATCH 3/4] Add check for branch to start with ci-auto-update --- run.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/run.py b/run.py index 09f2848..3b34294 100644 --- a/run.py +++ b/run.py @@ -1056,7 +1056,8 @@ async def github(request): # Nothing to do but success anyway (204 = No content) abort(204, "Nothing to do") # We only accept PRs that are created by github-action bot - if hook_infos["pull_request"]["user"]["login"] != "github-actions[bot]": + if hook_infos["pull_request"]["user"]["login"] != "github-actions[bot]" \ + or not hook_infos["pull_request"]["head"]["ref"].startswith("ci-auto-update-"): # Unauthorized abort(403, "Unauthorized") # Fetch the PR infos (yeah they ain't in the initial infos we get @_@) From 50eb3104296181a8cbda95ff33f537a1d0c7faa5 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 22 Sep 2021 15:32:22 +0200 Subject: [PATCH 4/4] PR webhooks: Return 204 if PR gets created by somebody else than github-actions --- run.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/run.py b/run.py index 3b34294..f9e4893 100644 --- a/run.py +++ b/run.py @@ -1059,7 +1059,7 @@ async def github(request): if hook_infos["pull_request"]["user"]["login"] != "github-actions[bot]" \ or not hook_infos["pull_request"]["head"]["ref"].startswith("ci-auto-update-"): # Unauthorized - abort(403, "Unauthorized") + abort(204, "Nothing to do") # Fetch the PR infos (yeah they ain't in the initial infos we get @_@) pr_infos_url = hook_infos["pull_request"]["url"]