diff --git a/.github/workflows/webservices-workflow-dispatch.yml b/.github/workflows/webservices-workflow-dispatch.yml index 431756118..78f3aceeb 100644 --- a/.github/workflows/webservices-workflow-dispatch.yml +++ b/.github/workflows/webservices-workflow-dispatch.yml @@ -31,6 +31,11 @@ on: description: 'a unique identifier for this run' required: true type: string + sha: + description: 'the sha of the commit to run on' + required: false + type: string + default: 'null' env: PY_COLORS: 1 @@ -78,7 +83,8 @@ jobs: conda-forge-webservices-init-task \ --task=${{ inputs.task }} \ --repo=${{ inputs.repo }} \ - --pr-number=${{ inputs.pr_number }} + --pr-number=${{ inputs.pr_number }} \ + --sha=${{ inputs.sha }} env: GH_TOKEN: ${{ secrets.CF_ADMIN_GITHUB_TOKEN }} @@ -122,7 +128,8 @@ jobs: --repo=${{ inputs.repo }} \ --pr-number=${{ inputs.pr_number }} \ --task-data-dir=${{ github.workspace }}/task-data \ - --requested-version=${{ inputs.requested_version }} + --requested-version=${{ inputs.requested_version }} \ + --sha=${{ inputs.sha }} - name: upload task data id: upload-task-data diff --git a/conda_forge_webservices/commands.py b/conda_forge_webservices/commands.py index cdb30958d..c3f23982b 100644 --- a/conda_forge_webservices/commands.py +++ b/conda_forge_webservices/commands.py @@ -22,7 +22,11 @@ LINT_VIA_GHA, ) from .update_teams import update_team -from .utils import ALLOWED_CMD_NON_FEEDSTOCKS, with_action_url +from .utils import ( + ALLOWED_CMD_NON_FEEDSTOCKS, + with_action_url, + get_workflow_run_from_uid, +) from ._version import __version__ from conda_forge_webservices.tokens import ( get_app_token_for_webservices_only, @@ -986,9 +990,37 @@ def make_rerender_dummy_commit(repo): return True +def set_rerender_pr_status(repo, pr_num, status, target_url=None, sha=None): + if target_url is not None: + kwargs = {"target_url": target_url} + else: + kwargs = {} + + if sha is None: + pull = repo.get_pull(int(pr_num)) + sha = pull.head.sha + commit = repo.get_commit(sha) + + if status == "success": + msg = "Rerendering successful." + elif status == "failure" or status == "error": + msg = "Rerendering failed." + else: + msg = "Rerendering in progress..." + + commit.create_status( + status, + description=msg, + context="conda-forge-rerendering-service", + **kwargs, + ) + + def rerender(full_name, pr_num): gh = get_gh_client() repo = gh.get_repo(full_name) + pull = repo.get_pull(int(pr_num)) + sha = pull.head.sha inject_app_token_into_feedstock(full_name, repo=repo) inject_app_token_into_feedstock_readonly(full_name, repo=repo) @@ -1007,8 +1039,17 @@ def rerender(full_name, pr_num): "pr_number": str(pr_num), "container_tag": ref, "uuid": uid, + "sha": sha, }, ) + if running: + run = get_workflow_run_from_uid(workflow, uid, ref) + if run: + target_url = run.html_url + else: + target_url = None + + set_rerender_pr_status(repo, pr_num, "pending", target_url=target_url, sha=sha) return not running diff --git a/conda_forge_webservices/github_actions_integration/__main__.py b/conda_forge_webservices/github_actions_integration/__main__.py index 105c1a39e..8d1d6680b 100644 --- a/conda_forge_webservices/github_actions_integration/__main__.py +++ b/conda_forge_webservices/github_actions_integration/__main__.py @@ -30,6 +30,7 @@ get_recipes_for_linting, ) from .version_updating import update_version, update_pr_title +from conda_forge_webservices.commands import set_rerender_pr_status LOGGER = logging.getLogger(__name__) @@ -55,7 +56,8 @@ def _pull_docker_image(): @click.option("--task", required=True, type=str) @click.option("--repo", required=True, type=str) @click.option("--pr-number", required=True, type=str) -def main_init_task(task, repo, pr_number): +@click.option("--sha", required=False, type=str, default=None) +def main_init_task(task, repo, pr_number, sha): logging.basicConfig(level=logging.INFO) action_desc = f"task `{task}` for conda-forge/{repo}#{pr_number}" @@ -69,8 +71,11 @@ def main_init_task(task, repo, pr_number): elif task == "lint": _, gh = create_api_sessions() gh_repo = gh.get_repo(f"conda-forge/{repo}") - pr = gh_repo.get_pull(int(pr_number)) - set_pr_status(pr.base.repo, pr.head.sha, "pending", target_url=None) + target_url = ( + f"https://github.com/conda-forge/conda-forge-webservices/" + f"actions/runs/{os.environ['GITHUB_RUN_ID']}" + ) + set_pr_status(gh_repo, sha, "pending", target_url=target_url) else: raise ValueError(f"Task `{task}` is not valid!") @@ -81,7 +86,8 @@ def main_init_task(task, repo, pr_number): @click.option("--pr-number", required=True, type=str) @click.option("--task-data-dir", required=True, type=str) @click.option("--requested-version", required=False, type=str, default=None) -def main_run_task(task, repo, pr_number, task_data_dir, requested_version): +@click.option("--sha", required=False, type=str, default=None) +def main_run_task(task, repo, pr_number, task_data_dir, requested_version, sha): setup_logging(level="DEBUG") LOGGER.info("running task `%s` for conda-forge/%s#%s", task, repo, pr_number) @@ -100,7 +106,13 @@ def main_run_task(task, repo, pr_number, task_data_dir, requested_version): git_repo.git.switch(f"pull/{pr_number}/head") prev_head = git_repo.active_branch.commit.hexsha - task_data = {"task": task, "repo": repo, "pr_number": pr_number, "task_results": {}} + task_data = { + "task": task, + "repo": repo, + "pr_number": pr_number, + "sha": sha, + "task_results": {}, + } if task == "rerender": _pull_docker_image() @@ -267,6 +279,7 @@ def main_finalize_task(task_data_dir): repo = task_data["repo"] pr_number = task_data["pr_number"] task_results = task_data["task_results"] + sha_for_status = task_data["sha"] LOGGER.info("finalizing task `%s` for conda-forge/%s#%s", task, repo, pr_number) LOGGER.info("task results:") @@ -354,6 +367,18 @@ def main_finalize_task(task_data_dir): repo_name=full_repo_name, close_pr_if_no_changes_or_errors=False, ) + status = "success" if not comment_push_error else "failure" + target_url = ( + f"https://github.com/conda-forge/conda-forge-webservices/" + f"actions/runs/{os.environ['GITHUB_RUN_ID']}" + ) + set_rerender_pr_status( + gh_repo, + int(pr_number), + status, + target_url=target_url, + sha=sha_for_status, + ) # if the pr was made by the bot, mark it as ready for review if ( @@ -464,7 +489,7 @@ def main_finalize_task(task_data_dir): gh, gh_repo, pr.number, task_results["lints"], task_results["hints"] ) - set_pr_status(pr.base.repo, pr.head.sha, status, target_url=msg.html_url) + set_pr_status(gh_repo, sha_for_status, status, target_url=msg.html_url) print(f"Linter status: {status}") print(f"Linter message:\n{msg.body}") diff --git a/conda_forge_webservices/linting.py b/conda_forge_webservices/linting.py index 2b0df2246..d1464ae8d 100644 --- a/conda_forge_webservices/linting.py +++ b/conda_forge_webservices/linting.py @@ -11,6 +11,7 @@ import conda_smithy.lint_recipe from conda_forge_webservices.tokens import get_gh_client +from conda_forge_webservices.utils import get_workflow_run_from_uid from ._version import __version__ LOGGER = logging.getLogger("conda_forge_webservices.linting") @@ -29,29 +30,6 @@ class LintInfo(TypedDict): sha: str -def _get_workflow_run_from_uid(workflow, uid, ref): - for _ in range(10): - time.sleep(1) - run = _inner_get_workflow_run_from_uid(workflow, uid, ref) - if run: - return run - return None - - -def _inner_get_workflow_run_from_uid(workflow, uid, ref): - num_try = 0 - max_try = 20 - for run in workflow.get_runs(branch=ref, event="workflow_dispatch"): - if uid in run.name: - return run - - num_try += 1 - if num_try > max_try: - break - - return None - - def lint_via_github_actions(full_name: str, pr_num: int) -> bool: gh = get_gh_client() repo = gh.get_repo(full_name) @@ -78,11 +56,12 @@ def lint_via_github_actions(full_name: str, pr_num: int) -> bool: "pr_number": str(pr_num), "container_tag": ref, "uuid": uid, + "sha": sha, }, ) if running: - run = _get_workflow_run_from_uid(workflow, uid, ref) + run = get_workflow_run_from_uid(workflow, uid, ref) if run: target_url = run.html_url else: diff --git a/conda_forge_webservices/utils.py b/conda_forge_webservices/utils.py index 5461a1a27..b9d69bcc2 100644 --- a/conda_forge_webservices/utils.py +++ b/conda_forge_webservices/utils.py @@ -1,5 +1,6 @@ import os import shutil +import time import tempfile from contextlib import contextmanager @@ -48,3 +49,26 @@ def with_action_url(msg: str) -> str: if action_url: msg += f"\n\nGenerated by {action_url}" return msg + + +def get_workflow_run_from_uid(workflow, uid, ref): + for _ in range(10): + time.sleep(1) + run = _inner_get_workflow_run_from_uid(workflow, uid, ref) + if run: + return run + return None + + +def _inner_get_workflow_run_from_uid(workflow, uid, ref): + num_try = 0 + max_try = 100 + for run in workflow.get_runs(branch=ref, event="workflow_dispatch"): + if uid in run.name: + return run + + num_try += 1 + if num_try > max_try: + break + + return None diff --git a/tests/test_live_linter.py b/tests/test_live_linter.py index 4513a22ce..9eddfadb4 100644 --- a/tests/test_live_linter.py +++ b/tests/test_live_linter.py @@ -5,7 +5,7 @@ import github import conda_forge_webservices -from conda_forge_webservices.linting import _get_workflow_run_from_uid +from conda_forge_webservices.utils import get_workflow_run_from_uid from conda_forge_webservices.github_actions_integration.linting import set_pr_status TEST_CASES = [ @@ -95,6 +95,7 @@ def test_linter_pr(pytestconfig): for pr_number, _, _ in TEST_CASES: uid = uuid.uuid4().hex pr = repo.get_pull(pr_number) + pr_sha = pr.head.sha workflow = repo.get_workflow("webservices-workflow-dispatch.yml") workflow_ran = workflow.create_dispatch( ref=branch, @@ -104,17 +105,18 @@ def test_linter_pr(pytestconfig): "pr_number": str(pr_number), "container_tag": conda_forge_webservices.__version__.replace("+", "."), "uuid": uid, + "sha": pr_sha, }, ) assert workflow_ran, f"Workflow did not run for PR {pr_number}!" - run = _get_workflow_run_from_uid(workflow, uid, branch) + run = get_workflow_run_from_uid(workflow, uid, branch) if run: target_url = run.html_url else: target_url = None assert target_url is not None print(f"target_url for PR {pr_number}: {target_url}", flush=True) - set_pr_status(repo, pr.head.sha, "pending", target_url=target_url) + set_pr_status(repo, pr_sha, "pending", target_url=target_url) print("\nsleeping for four minutes to let the linter work...", flush=True) tot = 0 diff --git a/tests/test_live_rerender.py b/tests/test_live_rerender.py index fafc90dad..90178dcfd 100644 --- a/tests/test_live_rerender.py +++ b/tests/test_live_rerender.py @@ -7,27 +7,48 @@ import conda_forge_webservices import github -from conda_forge_webservices.utils import pushd +from conda_forge_webservices.utils import pushd, get_workflow_run_from_uid +from conda_forge_webservices.commands import set_rerender_pr_status from conftest import _merge_main_to_branch def _run_test(branch): + pr_number = 445 print("sending workflow dispatch event to rerender...", flush=True) uid = uuid.uuid4().hex gh = github.Github(auth=github.Auth.Token(os.environ["GH_TOKEN"])) repo = gh.get_repo("conda-forge/conda-forge-webservices") workflow = repo.get_workflow("webservices-workflow-dispatch.yml") - workflow.create_dispatch( + pr_sha = ( + gh.get_repo("conda-forge/cf-autotick-bot-test-package-feedstock") + .get_pull(pr_number) + .head.sha + ) + running = workflow.create_dispatch( ref=branch, inputs={ "task": "rerender", "repo": "cf-autotick-bot-test-package-feedstock", - "pr_number": "445", + "pr_number": str(pr_number), "container_tag": conda_forge_webservices.__version__.replace("+", "."), "uuid": uid, + "sha": pr_sha, }, ) + assert running, f"Workflow dispatch failed for rerendering on PR {pr_number}!" + run = get_workflow_run_from_uid(workflow, uid, branch) + assert run, f"Workflow run not found for rerendering on PR {pr_number}!" + target_url = run.html_url + print(f"target_url for PR {pr_number}: {target_url}", flush=True) + + set_rerender_pr_status( + gh.get_repo("conda-forge/cf-autotick-bot-test-package-feedstock"), + pr_number, + "pending", + target_url=target_url, + sha=pr_sha, + ) print("sleeping for four minutes to let the rerender happen...", flush=True) tot = 0