bot/code_review_bot/cli.py (204 lines of code) (raw):

# This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. import argparse import os import sys from pathlib import Path import structlog import yaml from libmozdata.lando import LandoWarnings from libmozdata.phabricator import ( BuildState, PhabricatorAPI, UnitResult, UnitResultState, ) from code_review_bot import AnalysisException, stats, taskcluster from code_review_bot.config import settings from code_review_bot.report import get_reporters from code_review_bot.revisions import Revision from code_review_bot.tools.libmozdata import setup as setup_libmozdata from code_review_bot.tools.log import init_logger from code_review_bot.workflow import Workflow logger = structlog.get_logger(__name__) LANDO_FAILURE_MESSAGE = ( "Static analysis and linting did not run due to a generic failure." ) def parse_cli(): """ Setup CLI options parser """ parser = argparse.ArgumentParser(description="Mozilla Code Review Bot") parser.add_argument( "-c", "--configuration", help="Local configuration file replacing Taskcluster secrets", type=open, ) parser.add_argument( "--taskcluster-secret", help="Taskcluster Secret path", default=os.environ.get("TASKCLUSTER_SECRET"), ) parser.add_argument( "--mercurial-repository", help="Optional path to a up-to-date mercurial repository matching the analyzed revision.\n" "Reduce the time required to read updated files, i.e. to compute the unique hash of multiple issues.\n" "A clone is automatically performed when ingesting a revision and this option is unset, " "except on a developer instance (where HGMO is used).", type=Path, default=None, ) parser.add_argument("--taskcluster-client-id", help="Taskcluster Client ID") parser.add_argument("--taskcluster-access-token", help="Taskcluster Access token") return parser.parse_args() @stats.timer("runtime.analysis") def main(): args = parse_cli() taskcluster.auth(args.taskcluster_client_id, args.taskcluster_access_token) taskcluster.load_secrets( args.taskcluster_secret, prefixes=["common", "events", "code-review-bot", "bot"], required=( "APP_CHANNEL", "REPORTERS", "PHABRICATOR", "ALLOWED_PATHS", "repositories", ), existing={ "APP_CHANNEL": "development", "REPORTERS": [], "ZERO_COVERAGE_ENABLED": True, "ALLOWED_PATHS": ["*"], "task_failures_ignored": [], "ssh_key": None, "user_blacklist": [], }, local_secrets=yaml.safe_load(args.configuration) if args.configuration else None, ) init_logger( "bot", channel=taskcluster.secrets.get("APP_CHANNEL", "dev"), PAPERTRAIL_HOST=taskcluster.secrets.get("PAPERTRAIL_HOST"), PAPERTRAIL_PORT=taskcluster.secrets.get("PAPERTRAIL_PORT"), SENTRY_DSN=taskcluster.secrets.get("SENTRY_DSN"), ) # Setup libmozdata configuration setup_libmozdata("code-review-bot") # Setup settings before stats settings.setup( taskcluster.secrets["APP_CHANNEL"], taskcluster.secrets["ALLOWED_PATHS"], taskcluster.secrets["repositories"], taskcluster.secrets["ssh_key"], args.mercurial_repository, ) # Setup statistics influx_conf = taskcluster.secrets.get("influxdb") if influx_conf: stats.auth(influx_conf) # Load reporters reporters = get_reporters(taskcluster.secrets["REPORTERS"]) # Load index service index_service = taskcluster.get_service("index") # Load queue service queue_service = taskcluster.get_service("queue") # Load Phabricator API phabricator = taskcluster.secrets["PHABRICATOR"] phabricator_reporting_enabled = "phabricator" in reporters and phabricator.get( "publish", False ) phabricator_api = PhabricatorAPI(phabricator["api_key"], phabricator["url"]) if phabricator_reporting_enabled: reporters["phabricator"].setup_api(phabricator_api) # lando the Lando API lando_reporting_enabled = "lando" in reporters lando_api = None lando_publish_generic_failure = False if lando_reporting_enabled: if taskcluster.secrets["LANDO"].get("publish", False): lando_api = LandoWarnings( api_url=taskcluster.secrets["LANDO"]["url"], api_key=phabricator["api_key"], ) lando_publish_generic_failure = taskcluster.secrets["LANDO"][ "publish_failure" ] reporters["lando"].setup_api(lando_api) # We need Phabricator API to list black-listed users settings.load_user_blacklist(taskcluster.secrets["user_blacklist"], phabricator_api) # Load unique revision try: if settings.autoland_group_id: revision = Revision.from_decision_task( queue_service.task(settings.autoland_group_id), phabricator_api ) elif settings.mozilla_central_group_id: revision = Revision.from_decision_task( queue_service.task(settings.mozilla_central_group_id), phabricator_api ) elif settings.phabricator_build_target: revision = Revision.from_phabricator_trigger( settings.phabricator_build_target, phabricator_api, ) else: revision = Revision.from_try_task( queue_service.task(settings.try_task_id), queue_service.task(settings.try_group_id), phabricator_api, ) except Exception as e: # Report revision loading failure on production only # On testing or dev instances, we can use different Phabricator # configuration that do not match all the pulse messages sent if settings.on_production: raise else: logger.info( "Failed to load revision", task=settings.try_task_id, error=str(e), phabricator=phabricator["url"], ) return 1 # Run workflow according to source w = Workflow( reporters, index_service, queue_service, phabricator_api, taskcluster.secrets["ZERO_COVERAGE_ENABLED"], # Update build status only when phabricator reporting is enabled update_build=phabricator_reporting_enabled, task_failures_ignored=taskcluster.secrets["task_failures_ignored"], ) try: if settings.autoland_group_id: w.ingest_revision(revision, settings.autoland_group_id) elif settings.mozilla_central_group_id: w.ingest_revision(revision, settings.mozilla_central_group_id) elif settings.phabricator_build_target: w.start_analysis(revision) else: w.run(revision) except Exception as e: # Log errors to papertrail logger.error( "Static analysis failure", revision=revision, error=e, exc_info=True ) # Index analysis state extras = {} if isinstance(e, AnalysisException): extras["error_code"] = e.code extras["error_message"] = str(e) w.index(revision, state="error", **extras) # Update Phabricator failure = UnitResult( namespace="code-review", name="general", result=UnitResultState.Broken, details="WARNING: A generic error occurred in the code review bot.", format="remarkup", duration=0, ) if phabricator_reporting_enabled: w.phabricator.update_build_target( revision.build_target_phid, BuildState.Fail, unit=[failure] ) # Also update lando if not hasattr(revision, "id") or not hasattr(revision, "diff"): logger.info( "Skipping lando generic failure publication as the revision is incomplete" ) elif lando_publish_generic_failure: try: lando_api.del_all_warnings(revision.id, revision.diff["id"]) lando_api.add_warning( LANDO_FAILURE_MESSAGE, revision.id, revision.diff["id"] ) except Exception as ex: logger.error(str(ex), exc_info=True) # Then raise to mark task as erroneous raise return 0 if __name__ == "__main__": sys.exit(main())