diff --git a/src/cli/onefuzz/api.py b/src/cli/onefuzz/api.py index 852021e5f9..fdd9bd2197 100644 --- a/src/cli/onefuzz/api.py +++ b/src/cli/onefuzz/api.py @@ -528,6 +528,81 @@ def _download_tasks( azcopy_sync(to_download[name], outdir) +class Repro(Endpoint): + """Interact with repro files""" + + endpoint = "repro_vms" + + def get_files( + self, + report_container: primitives.Container, + report_name: str, + include_setup: bool = False, + output_dir: primitives.Directory = primitives.Directory("."), + ) -> None: + """downloads the files necessary to locally repro the crash from given report""" + report_bytes = self.onefuzz.containers.files.get(report_container, report_name) + report = json.loads(report_bytes) + + crash_info = { + "input_blob_container": primitives.Container(""), + "input_blob_name": "", + "job_id": "", + } + if "input_blob" in report: + crash_info["input_blob_container"] = report["input_blob"]["container"] + crash_info["input_blob_name"] = report["input_blob"]["name"] + crash_info["job_id"] = report["job_id"] + elif "crash_test_result" in report and "original_crash_test_result" in report: + if report["original_crash_test_result"]["crash_report"] is None: + self.logger.error( + "No crash report found in the original crash test result, repro files cannot be retrieved" + ) + return + elif report["crash_test_result"]["crash_report"] is None: + self.logger.info( + "No crash report found in the new crash test result, falling back on the original crash test result for job_id" + "Note: if using --include_setup, the downloaded fuzzer binaries may be out-of-date" + ) + + original_report = report["original_crash_test_result"]["crash_report"] + new_report = ( + report["crash_test_result"]["crash_report"] or original_report + ) # fallback on original_report + + crash_info["input_blob_container"] = original_report["input_blob"][ + "container" + ] + crash_info["input_blob_name"] = original_report["input_blob"]["name"] + crash_info["job_id"] = new_report["job_id"] + else: + self.logger.error( + "Encountered an unhandled report format, repro files cannot be retrieved" + ) + return + + self.logger.info( + "downloading files necessary to locally repro crash %s", + crash_info["input_blob_name"], + ) + self.onefuzz.containers.files.download( + primitives.Container(crash_info["input_blob_container"]), + crash_info["input_blob_name"], + os.path.join(output_dir, crash_info["input_blob_name"]), + ) + + if include_setup: + setup_container = list( + self.onefuzz.jobs.containers.list( + crash_info["job_id"], enums.ContainerType.setup + ) + )[0] + + self.onefuzz.containers.files.download_dir( + primitives.Container(setup_container), output_dir + ) + + class Notifications(Endpoint): """Interact with models.Notifications""" @@ -1588,6 +1663,7 @@ def __init__( client_secret=client_secret, ) self.containers = Containers(self) + self.repro = Repro(self) self.notifications = Notifications(self) self.tasks = Tasks(self) self.jobs = Jobs(self)