| # Copyright 2023 The ChromiumOS Authors | |
| # Use of this source code is governed by a BSD-style license that can be | |
| # found in the LICENSE file. | |
| """Copybot Controller. | |
| Handles the endpoint for running copybot and generating the protobuf. | |
| """ | |
| from pathlib import Path | |
| import tempfile | |
| from chromite.third_party.google.protobuf import json_format | |
| from chromite.api import controller | |
| from chromite.api import faux | |
| from chromite.api import validate | |
| from chromite.api.gen.chromite.api import copybot_pb2 | |
| from chromite.lib import constants | |
| from chromite.lib import cros_build_lib | |
| def _MockSuccess(_input_proto, _output_proto, _config_proto): | |
| """Mock success output for the RunCopybot endpoint.""" | |
| # Successful response is the default protobuf, so no need to fill it out. | |
| @faux.success(_MockSuccess) | |
| @faux.empty_error | |
| @validate.validation_complete | |
| def RunCopybot(input_proto, output_proto, _config_proto): | |
| """Run copybot. Translate all fields in the input protobuf to CLI args.""" | |
| cmd = [ | |
| constants.SOURCE_ROOT / "src/platform/dev/contrib/copybot/copybot.py" | |
| ] | |
| if input_proto.topic: | |
| cmd.extend(["--topic", input_proto.topic]) | |
| for label in input_proto.labels: | |
| cmd.extend(["--label", label.label]) | |
| for reviewer in input_proto.reviewers: | |
| cmd.extend(["--re", reviewer.user]) | |
| for cc in input_proto.ccs: | |
| cmd.extend(["--cc", cc.user]) | |
| if input_proto.prepend_subject: | |
| cmd.extend(["--prepend-subject", input_proto.prepend_subject]) | |
| if ( | |
| input_proto.merge_conflict_behavior | |
| == copybot_pb2.RunCopybotRequest.MERGE_CONFLICT_BEHAVIOR_SKIP | |
| ): | |
| cmd.extend(["--merge-conflict-behavior", "SKIP"]) | |
| if ( | |
| input_proto.merge_conflict_behavior | |
| == copybot_pb2.RunCopybotRequest.MERGE_CONFLICT_BEHAVIOR_FAIL | |
| ): | |
| cmd.extend(["--merge-conflict-behavior", "FAIL"]) | |
| for exclude in input_proto.exclude_file_patterns: | |
| cmd.extend(["--exclude-file-pattern", exclude.pattern]) | |
| for ph in input_proto.keep_pseudoheaders: | |
| cmd.extend(["--keep-pseudoheader", ph.name]) | |
| if input_proto.add_signed_off_by: | |
| cmd.append("--add-signed-off-by") | |
| if input_proto.dry_run: | |
| cmd.append("--dry-run") | |
| for po in input_proto.push_options: | |
| cmd.extend(["--push-option", po.opt]) | |
| cmd.append(f"{input_proto.upstream.url}:{input_proto.upstream.branch}") | |
| cmd.append(f"{input_proto.downstream.url}:{input_proto.downstream.branch}") | |
| with tempfile.TemporaryDirectory() as temp_dir: | |
| json_output_path = Path(temp_dir) / "copybot_output.json" | |
| cmd.extend(["--json-out", json_output_path]) | |
| try: | |
| cros_build_lib.run(cmd) | |
| except cros_build_lib.RunCommandError: | |
| # In case of failure, load details about the error from CopyBot's | |
| # JSON output into the output protobuf. (If CopyBot ran | |
| # successfully, the default values are simply used). CopyBot's | |
| # output matches the JSON representation of the RunCopybotResponse | |
| # protobuf. | |
| if not json_output_path.exists(): | |
| return controller.RETURN_CODE_UNRECOVERABLE | |
| json_format.Parse(json_output_path.read_text(), output_proto) | |
| return controller.RETURN_CODE_UNSUCCESSFUL_RESPONSE_AVAILABLE |