Skip to content

Commit

Permalink
Merge pull request #681 from conda-forge/revert-680-revert-679-lint-gha
Browse files Browse the repository at this point in the history
feat: move linting to GHA
  • Loading branch information
beckermr authored Sep 21, 2024
2 parents 74dc545 + 6ce6bea commit cc9b767
Show file tree
Hide file tree
Showing 10 changed files with 198 additions and 111 deletions.
65 changes: 38 additions & 27 deletions conda_forge_webservices/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,18 @@
import logging

# from .utils import tmp_directory
from .linting import compute_lint_message, comment_on_pr, set_pr_status
from .linting import (
compute_lint_message,
comment_on_pr,
set_pr_status,
lint_via_github_actions,
LINT_VIA_GHA,
)
from .update_teams import update_team
from .utils import ALLOWED_CMD_NON_FEEDSTOCKS, with_action_url
from conda_forge_webservices.tokens import (
get_app_token_for_webservices_only,
get_gh_client,
inject_app_token_into_feedstock,
inject_app_token_into_feedstock_readonly,
)
Expand Down Expand Up @@ -148,7 +155,7 @@ def add_reaction(
def pr_comment(org_name, repo_name, issue_num, comment, comment_id=None):
if not COMMAND_PREFIX.search(comment):
return
gh = github.Github(get_app_token_for_webservices_only())
gh = get_gh_client()
repo = gh.get_repo(f"{org_name}/{repo_name}")
pr = repo.get_pull(int(issue_num))
pr_detailed_comment(
Expand Down Expand Up @@ -178,10 +185,8 @@ def pr_detailed_comment(
if not (repo_name.endswith("-feedstock") or is_allowed_cmd):
return

GH_TOKEN = get_app_token_for_webservices_only()

if not is_allowed_cmd:
gh = github.Github(GH_TOKEN)
gh = get_gh_client()
repo = gh.get_repo(f"{org_name}/{repo_name}")
pull = repo.get_pull(int(pr_num))
if pull.head.repo.full_name.split("/")[0] == "conda-forge":
Expand All @@ -198,7 +203,7 @@ def pr_detailed_comment(
pull.create_issue_comment(message)

if RESTART_CI.search(comment):
gh = github.Github(GH_TOKEN)
gh = get_gh_client()
repo = gh.get_repo(f"{org_name}/{repo_name}")
if comment_id is not None or review_id is not None:
add_reaction("rocket", repo, pr_num, comment_id, review_id)
Expand All @@ -219,7 +224,7 @@ def pr_detailed_comment(
else:
team = repo_name.replace("-feedstock", "")

gh = github.Github(GH_TOKEN)
gh = get_gh_client()
repo = gh.get_repo(f"{org_name}/{repo_name}")
if comment_id is not None or review_id is not None:
add_reaction("rocket", repo, pr_num, comment_id, review_id)
Expand All @@ -232,7 +237,7 @@ def pr_detailed_comment(
pull.create_issue_comment(message)

if not is_allowed_cmd and RERUN_BOT.search(comment):
gh = github.Github(GH_TOKEN)
gh = get_gh_client()
repo = gh.get_repo(f"{org_name}/{repo_name}")
if comment_id is not None or review_id is not None:
add_reaction("rocket", repo, pr_num, comment_id, review_id)
Expand All @@ -252,16 +257,17 @@ def pr_detailed_comment(
return

if comment_id is not None or review_id is not None:
repo = github.Github(GH_TOKEN).get_repo(f"{org_name}/{repo_name}")
repo = get_gh_client().get_repo(f"{org_name}/{repo_name}")
add_reaction("rocket", repo, pr_num, comment_id, review_id)

tmp_dir = None
try:
tmp_dir = tempfile.mkdtemp("_recipe")

gh_token = get_app_token_for_webservices_only()
feedstock_dir = os.path.join(tmp_dir, repo_name)
repo_url = (
f"https://x-access-token:{GH_TOKEN}@github.com/{pr_owner}/{pr_repo}.git"
f"https://x-access-token:{gh_token}@github.com/{pr_owner}/{pr_repo}.git"
)

for _git_try_num in range(NUM_GIT_CLONE_TRIES):
Expand Down Expand Up @@ -342,7 +348,7 @@ def pr_detailed_comment(
""").format(doc_url) # noqa

if message is not None:
gh = github.Github(GH_TOKEN)
gh = get_gh_client()
gh_repo = gh.get_repo(f"{org_name}/{repo_name}")
pull = gh_repo.get_pull(int(pr_num))
pull.create_issue_comment(message)
Expand Down Expand Up @@ -385,8 +391,6 @@ def issue_comment(org_name, repo_name, issue_num, title, comment, comment_id=Non
if not any(command.search(text) for command in issue_commands):
return

APP_GH_TOKEN = get_app_token_for_webservices_only()

# sometimes the webhook outpaces other bits of the API so we try a bit
for i in range(NUM_GH_API_TRIES):
try:
Expand All @@ -405,7 +409,7 @@ def issue_comment(org_name, repo_name, issue_num, title, comment, comment_id=Non
raise e

# these are used when the app takes actions
app_repo = github.Github(APP_GH_TOKEN).get_repo(f"{org_name}/{repo_name}")
app_repo = get_gh_client().get_repo(f"{org_name}/{repo_name}")
app_issue = app_repo.get_issue(int(issue_num))

if comment_id is not None:
Expand Down Expand Up @@ -458,11 +462,12 @@ def issue_comment(org_name, repo_name, issue_num, title, comment, comment_id=Non
gh,
)

gh_token = get_app_token_for_webservices_only()
feedstock_dir = os.path.join(tmp_dir, repo_name)
repo_url = "https://x-access-token:{}@github.com/{}/{}.git".format(
os.environ["GH_TOKEN"], forked_user, repo_name
)
upstream_repo_url = f"https://x-access-token:{APP_GH_TOKEN}@github.com/{org_name}/{repo_name}.git"
upstream_repo_url = f"https://x-access-token:{gh_token}@github.com/{org_name}/{repo_name}.git"

for _git_try_num in range(NUM_GIT_CLONE_TRIES):
try:
Expand Down Expand Up @@ -971,7 +976,7 @@ def make_rerender_dummy_commit(repo):


def rerender(full_name, pr_num):
gh = github.Github(get_app_token_for_webservices_only())
gh = get_gh_client()
repo = gh.get_repo(full_name)

inject_app_token_into_feedstock(full_name, repo=repo)
Expand All @@ -984,7 +989,7 @@ def rerender(full_name, pr_num):


def update_version(full_name, pr_num, input_ver):
gh = github.Github(get_app_token_for_webservices_only())
gh = get_gh_client()
repo = gh.get_repo(full_name)

inject_app_token_into_feedstock(full_name, repo=repo)
Expand Down Expand Up @@ -1023,17 +1028,23 @@ def make_noarch(repo):

def relint(owner, repo_name, pr_num):
pr = int(pr_num)
lint_info = compute_lint_message(
owner,
repo_name,
pr,
repo_name == "staged-recipes",
)
if not lint_info:
LOGGER.warning("Linting was skipped.")
if LINT_VIA_GHA:
lint_via_github_actions(
f"{owner}/{repo_name}",
pr,
)
else:
msg = comment_on_pr(owner, repo_name, pr, lint_info["message"], force=True)
set_pr_status(owner, repo_name, lint_info, target_url=msg.html_url)
lint_info = compute_lint_message(
owner,
repo_name,
pr,
repo_name == "staged-recipes",
)
if not lint_info:
LOGGER.warning("Linting was skipped.")
else:
msg = comment_on_pr(owner, repo_name, pr, lint_info["message"], force=True)
set_pr_status(owner, repo_name, lint_info, target_url=msg.html_url)


def add_bot_rerun_label(repo, pr_num):
Expand Down
10 changes: 6 additions & 4 deletions conda_forge_webservices/feedstock_outputs.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,10 @@
import binstar_client.errors

from .utils import parse_conda_pkg
from conda_forge_webservices.tokens import get_app_token_for_webservices_only
from conda_forge_webservices.tokens import (
get_app_token_for_webservices_only,
get_gh_client,
)

LOGGER = logging.getLogger("conda_forge_webservices.feedstock_outputs")

Expand Down Expand Up @@ -217,8 +220,7 @@ def _add_feedstock_output(
feedstock: str,
pkg_name: str,
):
gh_token = get_app_token_for_webservices_only()
gh = github.Github(auth=github.Auth.Token(gh_token))
gh = get_gh_client()
repo = gh.get_repo("conda-forge/feedstock-outputs")
try:
contents = repo.get_contents(_get_sharded_path(pkg_name))
Expand Down Expand Up @@ -431,7 +433,7 @@ def comment_on_outputs_copy(feedstock, git_sha, errors, valid, copied):
if not feedstock.endswith("-feedstock"):
return None

gh = github.Github(get_app_token_for_webservices_only())
gh = get_gh_client()

team_name = feedstock[: -len("-feedstock")]

Expand Down
8 changes: 5 additions & 3 deletions conda_forge_webservices/feedstocks_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@
import tempfile
import shutil
import logging
import github

from conda_forge_webservices.tokens import get_app_token_for_webservices_only
from conda_forge_webservices.tokens import (
get_app_token_for_webservices_only,
get_gh_client,
)
from conda_forge_webservices.utils import with_action_url

LOGGER = logging.getLogger("conda_forge_webservices.feedstocks_service")
Expand Down Expand Up @@ -43,7 +45,7 @@ def update_feedstock(org_name, repo_name):
# sometimes the webhook outpaces other bits of the API so we try a bit
for i in range(5):
try:
gh = github.Github(gh_token)
gh = get_gh_client()
default_branch = gh.get_repo(f"{org_name}/{repo_name}").default_branch
break
except Exception as e:
Expand Down
50 changes: 37 additions & 13 deletions conda_forge_webservices/linting.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,18 @@
from typing import TypedDict

from git import GitCommandError, Repo
import github
import conda_smithy.lint_recipe

from conda_forge_webservices.tokens import get_app_token_for_webservices_only
from conda_forge_webservices.tokens import get_gh_client

LOGGER = logging.getLogger("conda_forge_webservices.linting")
SKIP_MSGS = [
"[ci skip]",
"[skip ci]",
"[lint skip]",
"[skip lint]",
]
LINT_VIA_GHA = True


class LintInfo(TypedDict):
Expand All @@ -21,6 +27,30 @@ class LintInfo(TypedDict):
sha: str


def lint_via_github_actions(full_name: str, pr_num: int) -> bool:
gh = get_gh_client()
repo = gh.get_repo(full_name)
repo_owner, repo_name = full_name.split("/")
pr = repo.get_pull(pr_num)
sha = pr.head.sha
commit = gh.get_repo(pr.head.repo.full_name).get_git_commit(sha)
commit_msg = commit.message

should_skip = any([msg in commit_msg for msg in SKIP_MSGS])
if should_skip:
return False

running = repo.create_repository_dispatch(
"lint",
client_payload={"pr": pr_num},
)

if running:
_set_pr_status(repo_owner, repo_name, sha, "pending")

return running


def find_recipes(path: Path) -> list[Path]:
"""
Returns all `meta.yaml` and `recipe.yaml` files in the given path.
Expand Down Expand Up @@ -155,7 +185,7 @@ def _set_pr_status(
else:
kwargs = {}

gh = github.Github(get_app_token_for_webservices_only())
gh = get_gh_client()
user = gh.get_user(owner)
repo = user.get_repo(repo_name)
commit = repo.get_commit(sha)
Expand All @@ -174,7 +204,7 @@ def compute_lint_message(
ignore_base: bool = False,
set_pending_status: bool = True,
) -> LintInfo | None:
gh = github.Github(get_app_token_for_webservices_only())
gh = get_gh_client()

owner = gh.get_user(repo_owner)
remote_repo = owner.get_repo(repo_name)
Expand Down Expand Up @@ -214,14 +244,8 @@ def compute_lint_message(
sha = str(ref_head.commit.hexsha)

# Check if the linter is skipped via the commit message.
skip_msgs = [
"[ci skip]",
"[skip ci]",
"[lint skip]",
"[skip lint]",
]
commit_msg = repo.commit(sha).message
should_skip = any([msg in commit_msg for msg in skip_msgs])
should_skip = any([msg in commit_msg for msg in SKIP_MSGS])
if should_skip:
return None

Expand Down Expand Up @@ -285,7 +309,7 @@ def comment_on_pr(
force: bool = False,
search: str | None = None,
):
gh = github.Github(get_app_token_for_webservices_only())
gh = get_gh_client()

user = gh.get_user(owner)
repo = user.get_repo(repo_name)
Expand Down Expand Up @@ -324,7 +348,7 @@ def comment_on_pr(
def set_pr_status(
owner: str, repo_name: str, lint_info: LintInfo, target_url: str | None = None
):
gh = github.Github(get_app_token_for_webservices_only())
gh = get_gh_client()

user = gh.get_user(owner)
repo = user.get_repo(repo_name)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,21 +66,21 @@ def test_ok_recipe_above_good_recipe():
lint = compute_lint_message(
"conda-forge", "conda-forge-webservices", 54, set_pending_status=False
)
assert expected_message == lint["message"]
assert lint["message"].startswith(expected_message)


def test_ok_recipe_beside_good_recipe():
expected_message = textwrap.dedent("""
Hi! This is the friendly automated conda-forge-linting service.
I just wanted to let you know that I linted all conda-recipes in your PR (```recipe/meta.yaml```, ```recipes/recipe/meta.yaml```) and found it was in an excellent condition.
I just wanted to let you know that I linted all conda-recipes in your PR (```recipe/blah/meta.yaml```, ```recipe/meta.yaml```, ```recipes/recipe/meta.yaml```) and found it was in an excellent condition.
""") # noqa

lint = compute_lint_message(
"conda-forge", "conda-forge-webservices", 62, set_pending_status=False
)
assert expected_message == lint["message"]
assert lint["message"].startswith(expected_message)


def test_ok_recipe_above_ignored_good_recipe():
Expand All @@ -94,21 +94,21 @@ def test_ok_recipe_above_ignored_good_recipe():
lint = compute_lint_message(
"conda-forge", "conda-forge-webservices", 54, True, set_pending_status=False
)
assert expected_message == lint["message"]
assert lint["message"].startswith(expected_message)


def test_ok_recipe_beside_ignored_good_recipe():
expected_message = textwrap.dedent("""
Hi! This is the friendly automated conda-forge-linting service.
I just wanted to let you know that I linted all conda-recipes in your PR (```recipe/meta.yaml```) and found it was in an excellent condition.
I just wanted to let you know that I linted all conda-recipes in your PR (```recipe/blah/meta.yaml```, ```recipe/meta.yaml```) and found it was in an excellent condition.
""") # noqa

lint = compute_lint_message(
"conda-forge", "conda-forge-webservices", 62, True, set_pending_status=False
)
assert expected_message == lint["message"]
assert lint["message"].startswith(expected_message)


def test_conflict_ok_recipe():
Expand Down
Loading

0 comments on commit cc9b767

Please sign in to comment.