diff --git a/app.cfg.example b/app.cfg.example index 7a6a1073..1888e499 100644 --- a/app.cfg.example +++ b/app.cfg.example @@ -219,3 +219,13 @@ no_matching_tarball = No tarball matching `{tarball_pattern}` found in job dir. multiple_tarballs = Found {num_tarballs} tarballs in job dir - only 1 matching `{tarball_pattern}` expected. job_result_unknown_fmt =
:shrug: UNKNOWN _(click triangle for detailed information)_
job_test_unknown_fmt =
:shrug: UNKNOWN _(click triangle for detailed information)_
+ +[download_pr_comments] +git_clone_failure = Unable to clone the target repository. +git_clone_tip = _Tip: This could be a connection failure. Try again and if the issue remains check if the address is correct_. +git_checkout_failure = Unable to checkout to the correct branch. +git_checkout_tip = _Tip: Ensure that the branch name is correct and the target branch is available._ +curl_failure = Unable to download the `.diff` file. +curl_tip = _Tip: This could be a connection failure. Try again and if the issue remains check if the address is correct_ +git_apply_failure = Unable to download or merge changes between the source branch and the destination branch. +git_apply_tip = _Tip: This can usually be resolved by syncing your branch and resolving any merge conflicts._ diff --git a/tasks/build.py b/tasks/build.py index 1ce8e956..473599b6 100644 --- a/tasks/build.py +++ b/tasks/build.py @@ -40,9 +40,22 @@ BUILD_PERMISSION = "build_permission" CFG_DIRNAME = "cfg" CONTAINER_CACHEDIR = "container_cachedir" +CURL_FAILURE = "curl_failure" +CURL_TIP = "curl_tip" CVMFS_CUSTOMIZATIONS = "cvmfs_customizations" DEFAULT_JOB_TIME_LIMIT = "24:00:00" +DOWNLOAD_PR_COMMENTS = "download_pr_comments" +ERROR_CURL = "curl" +ERROR_GIT_APPLY = "git apply" +ERROR_GIT_CHECKOUT = "git checkout" +ERROR_GIT_CLONE = "curl" GITHUB = "github" +GIT_CLONE_FAILURE = "git_clone_failure" +GIT_CLONE_TIP = "git_clone_tip" +GIT_CHECKOUT_FAILURE = "git_checkout_failure" +GIT_CHECKOUT_TIP = "git_checkout_tip" +GIT_APPLY_FAILURE = "git_apply_failure" +GIT_APPLY_TIP = "git_apply_tip" HTTPS_PROXY = "https_proxy" HTTP_PROXY = "http_proxy" INITIAL_COMMENT = "initial_comment" @@ -337,7 +350,9 @@ def download_pr(repo_name, branch_name, pr, arch_job_dir): arch_job_dir (string): working directory of the job to be submitted Returns: - None (implicitly) + None (implicitly), in case an error is caught in the git clone, git checkout, curl, + or git apply commands, returns the output, stderror, exit code and a string + stating which of these commands failed. """ # download pull request to arch_job_dir # - 'git clone' repository into arch_job_dir (NOTE 'git clone' requires that @@ -346,20 +361,91 @@ def download_pr(repo_name, branch_name, pr, arch_job_dir): # - 'curl' diff for pull request # - 'git apply' diff file git_clone_cmd = ' '.join(['git clone', f'https://github.com/{repo_name}', arch_job_dir]) - clone_output, clone_error, clone_exit_code = run_cmd(git_clone_cmd, "Clone repo", arch_job_dir) + log(f'cloning with command {git_clone_cmd}') + clone_output, clone_error, clone_exit_code = run_cmd( + git_clone_cmd, "Clone repo", arch_job_dir, raise_on_error=False + ) + if clone_exit_code != 0: + error_stage = ERROR_GIT_CLONE + return clone_output, clone_error, clone_exit_code, error_stage git_checkout_cmd = ' '.join([ 'git checkout', branch_name, ]) - checkout_output, checkout_err, checkout_exit_code = run_cmd(git_checkout_cmd, - "checkout branch '%s'" % branch_name, arch_job_dir) + log(f'checking out with command {git_checkout_cmd}') + checkout_output, checkout_err, checkout_exit_code = run_cmd( + git_checkout_cmd, "checkout branch '%s'" % branch_name, arch_job_dir, raise_on_error=False + ) + if checkout_exit_code != 0: + error_stage = ERROR_GIT_CHECKOUT + return checkout_output, checkout_err, checkout_exit_code, error_stage curl_cmd = f'curl -L https://github.com/{repo_name}/pull/{pr.number}.diff > {pr.number}.diff' - curl_output, curl_error, curl_exit_code = run_cmd(curl_cmd, "Obtain patch", arch_job_dir) + log(f'curl with command {curl_cmd}') + curl_output, curl_error, curl_exit_code = run_cmd( + curl_cmd, "Obtain patch", arch_job_dir, raise_on_error=False + ) + if curl_exit_code != 0: + error_stage = ERROR_CURL + return curl_output, curl_error, curl_exit_code, error_stage git_apply_cmd = f'git apply {pr.number}.diff' - git_apply_output, git_apply_error, git_apply_exit_code = run_cmd(git_apply_cmd, "Apply patch", arch_job_dir) + log(f'git apply with command {git_apply_cmd}') + git_apply_output, git_apply_error, git_apply_exit_code = run_cmd( + git_apply_cmd, "Apply patch", arch_job_dir, raise_on_error=False + ) + if git_apply_exit_code != 0: + error_stage = ERROR_GIT_APPLY + return git_apply_output, git_apply_error, git_apply_exit_code, error_stage + + +def comment_download_pr(base_repo_name, pr, download_pr_exit_code, download_pr_error, error_stage): + """ + Handle download_pr() exit code and write helpful comment to PR in case of failure + + Args: + base_repo_name (string): name of the repository (format USER_OR_ORGANISATION/REPOSITORY) + pr (github.PullRequest.PullRequest): instance representing the pull request + download_pr_exit_code (int): exit code from download_pr(). 0 if all tasks were successful, + otherwise it corresponds to the error codes of git clone, git checkout, git apply, or curl. + download_pr_error (string): none, or the output of stderr from git clone, git checkout, git apply or curl. + error_stage (string): a string informing the stage where download_pr() failed. Can be 'git clone', + 'git checkout', 'curl', or 'git apply'. + + Return: + None (implicitly). A comment is created in the appropriate PR. + + """ + if download_pr_exit_code != 0: + fn = sys._getframe().f_code.co_name + + download_pr_comments_cfg = config.read_config()[DOWNLOAD_PR_COMMENTS] + if error_stage == ERROR_GIT_CLONE: + download_comment = (f"`{download_pr_error}`" + f"{download_pr_comments_cfg[GIT_CLONE_FAILURE]}" + f"{download_pr_comments_cfg[GIT_CLONE_TIP]}") + elif error_stage == ERROR_GIT_CHECKOUT: + download_comment = (f"`{download_pr_error}`" + f"{download_pr_comments_cfg[GIT_CHECKOUT_FAILURE]}" + f"{download_pr_comments_cfg[GIT_CHECKOUT_TIP]}") + elif error_stage == ERROR_CURL: + download_comment = (f"`{download_pr_error}`" + f"{download_pr_comments_cfg[CURL_FAILURE]}" + f"{download_pr_comments_cfg[CURL_TIP]}") + elif error_stage == ERROR_GIT_APPLY: + download_comment = (f"`{download_pr_error}`" + f"{download_pr_comments_cfg[GIT_APPLY_FAILURE]}" + f"{download_pr_comments_cfg[GIT_APPLY_TIP]}") + + download_comment = pr_comments.create_comment( + repo_name=base_repo_name, pr_number=pr.number, comment=download_comment + ) + if download_comment: + log(f"{fn}(): created PR issue comment with id {download_comment.id}") + else: + log(f"{fn}(): failed to create PR issue comment") + raise ValueError("Unable to download PR and/or sync changes") def apply_cvmfs_customizations(cvmfs_customizations, arch_job_dir): @@ -454,8 +540,10 @@ def prepare_jobs(pr, cfg, event_info, action_filter): log(f"{fn}(): job_dir '{job_dir}'") # TODO optimisation? download once, copy and cleanup initial copy? - download_pr(base_repo_name, base_branch_name, pr, job_dir) - + download_pr_output, download_pr_error, download_pr_exit_code, error_stage = download_pr( + base_repo_name, base_branch_name, pr, job_dir + ) + comment_download_pr(base_repo_name, pr, download_pr_exit_code, download_pr_error, error_stage) # prepare job configuration file 'job.cfg' in directory /cfg cpu_target = '/'.join(arch.split('/')[1:]) os_type = arch.split('/')[0]