#!/usr/bin/env python3 import json import logging import os import sys import atexit from typing import Dict, List, Tuple from github import Github from env_helper import ( GITHUB_JOB_URL, GITHUB_REPOSITORY, GITHUB_RUN_URL, GITHUB_SERVER_URL, REPORTS_PATH, TEMP_PATH, ) from report import create_build_html_report, BuildResult, BuildResults from s3_helper import S3Helper from get_robot_token import get_best_robot_token from pr_info import NeedsDataType, PRInfo from commit_status_helper import ( RerunHelper, format_description, get_commit, post_commit_status, update_mergeable_check, ) from ci_config import CI_CONFIG NEEDS_DATA_PATH = os.getenv("NEEDS_DATA_PATH", "") def group_by_artifacts(build_urls: List[str]) -> Dict[str, List[str]]: groups = { "apk": [], "deb": [], "binary": [], "tgz": [], "rpm": [], "performance": [], } # type: Dict[str, List[str]] for url in build_urls: if url.endswith("performance.tar.zst"): groups["performance"].append(url) elif ( url.endswith(".deb") or url.endswith(".buildinfo") or url.endswith(".changes") or url.endswith(".tar.gz") ): groups["deb"].append(url) elif url.endswith(".apk"): groups["apk"].append(url) elif url.endswith(".rpm"): groups["rpm"].append(url) elif url.endswith(".tgz") or url.endswith(".tgz.sha512"): groups["tgz"].append(url) else: groups["binary"].append(url) return groups def get_failed_report( job_name: str, ) -> Tuple[BuildResults, List[List[str]], List[str]]: message = f"{job_name} failed" build_result = BuildResult( compiler="unknown", debug_build=False, sanitizer="unknown", status=message, elapsed_seconds=0, comment="", ) return [build_result], [[""]], [GITHUB_RUN_URL] def process_report( build_report: dict, ) -> Tuple[BuildResults, List[List[str]], List[str]]: build_config = build_report["build_config"] build_result = BuildResult( compiler=build_config["compiler"], debug_build=build_config["debug_build"], sanitizer=build_config["sanitizer"], status="success" if build_report["status"] else "failure", elapsed_seconds=build_report["elapsed_seconds"], comment=build_config["comment"], ) build_results = [] build_urls = [] build_logs_urls = [] urls_groups = group_by_artifacts(build_report["build_urls"]) found_group = False for _, group_urls in urls_groups.items(): if group_urls: build_results.append(build_result) build_urls.append(group_urls) build_logs_urls.append(build_report["log_url"]) found_group = True # No one group of urls is found, a failed report if not found_group: build_results.append(build_result) build_urls.append([""]) build_logs_urls.append(build_report["log_url"]) return build_results, build_urls, build_logs_urls def get_build_name_from_file_name(file_name): return file_name.replace("build_urls_", "").replace(".json", "") def main(): logging.basicConfig(level=logging.INFO) temp_path = TEMP_PATH logging.info("Reports path %s", REPORTS_PATH) if not os.path.exists(temp_path): os.makedirs(temp_path) build_check_name = sys.argv[1] needs_data = {} # type: NeedsDataType required_builds = 0 if os.path.exists(NEEDS_DATA_PATH): with open(NEEDS_DATA_PATH, "rb") as file_handler: needs_data = json.load(file_handler) required_builds = len(needs_data) if needs_data and all(i["result"] == "skipped" for i in needs_data.values()): logging.info("All builds are skipped, exiting") sys.exit(0) logging.info("The next builds are required: %s", ", ".join(needs_data)) gh = Github(get_best_robot_token(), per_page=100) pr_info = PRInfo() commit = get_commit(gh, pr_info.sha) atexit.register(update_mergeable_check, gh, pr_info, build_check_name) rerun_helper = RerunHelper(commit, build_check_name) if rerun_helper.is_already_finished_by_status(): logging.info("Check is already finished according to github status, exiting") sys.exit(0) builds_for_check = CI_CONFIG["builds_report_config"][build_check_name] required_builds = required_builds or len(builds_for_check) # Collect reports from json artifacts builds_report_map = {} for root, _, files in os.walk(REPORTS_PATH): for f in files: if f.startswith("build_urls_") and f.endswith(".json"): logging.info("Found build report json %s", f) build_name = get_build_name_from_file_name(f) if build_name in builds_for_check: with open(os.path.join(root, f), "rb") as file_handler: builds_report_map[build_name] = json.load(file_handler) else: logging.info( "Skipping report %s for build %s, it's not in our reports list", f, build_name, ) # Sort reports by config order build_reports = [ builds_report_map[build_name] for build_name in builds_for_check if build_name in builds_report_map ] some_builds_are_missing = len(build_reports) < required_builds missing_build_names = [] if some_builds_are_missing: logging.warning( "Expected to get %s build results, got only %s", required_builds, len(build_reports), ) missing_build_names = [ name for name in needs_data if not any(rep for rep in build_reports if rep["job_name"] == name) ] else: logging.info("Got exactly %s builds", len(builds_report_map)) # Group build artifacts by groups build_results = [] # type: BuildResults build_artifacts = [] # type: List[List[str]] build_logs = [] # type: List[str] for build_report in build_reports: _build_results, build_artifacts_url, build_logs_url = process_report( build_report ) logging.info( "Got %s artifact groups for build report report", len(_build_results) ) build_results.extend(_build_results) build_artifacts.extend(build_artifacts_url) build_logs.extend(build_logs_url) for failed_job in missing_build_names: _build_results, build_artifacts_url, build_logs_url = get_failed_report( failed_job ) build_results.extend(_build_results) build_artifacts.extend(build_artifacts_url) build_logs.extend(build_logs_url) total_groups = len(build_results) logging.info("Totally got %s artifact groups", total_groups) if total_groups == 0: logging.error("No success builds, failing check") sys.exit(1) s3_helper = S3Helper() branch_url = f"{GITHUB_SERVER_URL}/{GITHUB_REPOSITORY}/commits/master" branch_name = "master" if pr_info.number != 0: branch_name = f"PR #{pr_info.number}" branch_url = f"{GITHUB_SERVER_URL}/{GITHUB_REPOSITORY}/pull/{pr_info.number}" commit_url = f"{GITHUB_SERVER_URL}/{GITHUB_REPOSITORY}/commit/{pr_info.sha}" task_url = GITHUB_JOB_URL() report = create_build_html_report( build_check_name, build_results, build_logs, build_artifacts, task_url, branch_url, branch_name, commit_url, ) report_path = os.path.join(temp_path, "report.html") with open(report_path, "w", encoding="utf-8") as fd: fd.write(report) logging.info("Going to upload prepared report") context_name_for_path = build_check_name.lower().replace(" ", "_") s3_path_prefix = ( str(pr_info.number) + "/" + pr_info.sha + "/" + context_name_for_path ) url = s3_helper.upload_test_report_to_s3( report_path, s3_path_prefix + "/report.html" ) logging.info("Report url %s", url) print(f"::notice ::Report url: {url}") # Prepare a commit status ok_groups = 0 summary_status = "success" for build_result in build_results: if build_result.status == "failure" and summary_status != "error": summary_status = "failure" if build_result.status == "error" or not build_result.status: summary_status = "error" if build_result.status == "success": ok_groups += 1 # Check if there are no builds at all, do not override bad status if summary_status == "success": if some_builds_are_missing: summary_status = "pending" elif ok_groups == 0: summary_status = "error" addition = "" if some_builds_are_missing: addition = f" ({len(build_reports)} of {required_builds} builds are OK)" description = format_description( f"{ok_groups}/{total_groups} artifact groups are OK{addition}" ) post_commit_status( commit, summary_status, url, description, build_check_name, pr_info ) if summary_status == "error": sys.exit(1) if __name__ == "__main__": main()