bot/code_review_bot/config.py (142 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 atexit import collections import fnmatch import os import shutil import tempfile from pathlib import Path import pkg_resources import structlog REPO_MOZILLA_CENTRAL = "https://hg.mozilla.org/mozilla-central" REPO_AUTOLAND = "https://hg.mozilla.org/integration/autoland" logger = structlog.get_logger(__name__) TaskCluster = collections.namedtuple( "TaskCluster", "results_dir, task_id, run_id, local" ) RepositoryConf = collections.namedtuple( "RepositoryConf", "name, try_name, url, try_url, decision_env_prefix, ssh_user", ) def GetAppUserAgent(): return {"user-agent": f"code-review-bot/{settings.version}"} class Settings: def __init__(self): self.config = { "cpp_extensions": frozenset([".c", ".cpp", ".cc", ".cxx", ".m", ".mm"]), "cpp_header_extensions": frozenset([".h", ".hh", ".hpp", ".hxx"]), "java_extensions": frozenset([".java"]), "idl_extensions": frozenset([".idl"]), "js_extensions": frozenset([".js", ".jsm"]), } self.app_channel = None self.taskcluster = None self.try_task_id = None self.try_group_id = None self.autoland_group_id = None self.mozilla_central_group_id = None self.phabricator_build_target = None self.repositories = [] self.decision_env_prefixes = [] # Max number of issues published to the backend at a time during the ingestion of a revision self.bulk_issue_chunks = 100 # Cache to store file-by-file from HGMO Rest API self.hgmo_cache = tempfile.mkdtemp(suffix="hgmo") # Cache to store whole repositories self.mercurial_cache = None # SSH Key used to push on try self.ssh_key = None # List of users that should trigger a new analysis # Indexed by their Phabricator ID self.user_blacklist = {} # Always cleanup at the end of the execution atexit.register(self.cleanup) # caching the versions of the app self.version = pkg_resources.require("code-review-bot")[0].version def setup( self, app_channel, allowed_paths, repositories, ssh_key=None, mercurial_cache=None, ): # Detect source from env if "TRY_TASK_ID" in os.environ and "TRY_TASK_GROUP_ID" in os.environ: self.try_task_id = os.environ["TRY_TASK_ID"] self.try_group_id = os.environ["TRY_TASK_GROUP_ID"] elif "AUTOLAND_TASK_GROUP_ID" in os.environ: self.autoland_group_id = os.environ["AUTOLAND_TASK_GROUP_ID"] elif "MOZILLA_CENTRAL_TASK_GROUP_ID" in os.environ: self.mozilla_central_group_id = os.environ["MOZILLA_CENTRAL_TASK_GROUP_ID"] elif "PHABRICATOR_BUILD_TARGET" in os.environ: # Setup trigger mode using Phabricator information self.phabricator_build_target = os.environ["PHABRICATOR_BUILD_TARGET"] assert self.phabricator_build_target.startswith( "PHID-HMBT" ), f"Not a phabrication build target PHID: {self.phabricator_build_target}" else: raise Exception("Only TRY mode is supported") self.app_channel = app_channel # Save Taskcluster ID for logging if "TASK_ID" in os.environ and "RUN_ID" in os.environ: self.taskcluster = TaskCluster( "/tmp/results", os.environ["TASK_ID"], os.environ["RUN_ID"], False ) else: self.taskcluster = TaskCluster( tempfile.mkdtemp(), "local instance", 0, True ) if not os.path.isdir(self.taskcluster.results_dir): os.makedirs(self.taskcluster.results_dir) if "BULK_ISSUE_CHUNKS" in os.environ: self.bulk_issue_chunks = int(os.environ["BULK_ISSUE_CHUNKS"]) # Save allowed paths assert isinstance(allowed_paths, list) assert all(map(lambda p: isinstance(p, str), allowed_paths)) self.allowed_paths = allowed_paths # Build available repositories from secret def build_conf(nb, repo): assert isinstance( repo, dict ), "Repository configuration #{nb+1} is not a dict" data = [] for key in RepositoryConf._fields: assert ( key in repo ), f"Missing key {key} in repository configuration #{nb+1}" data.append(repo[key]) return RepositoryConf._make(data) self.repositories = [build_conf(i, repo) for i, repo in enumerate(repositories)] assert self.repositories, "No repositories available" # Save prefixes for decision environment variables self.decision_env_prefixes = [ repo.decision_env_prefix for repo in self.repositories ] # Store mercurial cache path if mercurial_cache is not None: self.mercurial_cache = Path(mercurial_cache) assert ( self.mercurial_cache.exists() ), f"Mercurial cache does not exist {self.mercurial_cache}" logger.info("Using mercurial cache", path=self.mercurial_cache) # Save ssh key when mercurial cache is enabled self.ssh_key = ssh_key def load_user_blacklist(self, usernames, phabricator_api): """ Load all black listed users from Phabricator API """ self.user_blacklist = { user["phid"]: user["fields"]["username"] for user in phabricator_api.search_users( constraints={"usernames": usernames} ) } logger.info("Blacklisted users", names=self.user_blacklist.values()) def __getattr__(self, key): if key not in self.config: raise AttributeError return self.config[key] @property def on_production(self): """ Are we running on production ? """ return self.app_channel == "production" and self.taskcluster.local is False @property def mercurial_cache_checkout(self): """ When local mercurial cache is enabled, path to the checkout """ if self.mercurial_cache is None: return return self.mercurial_cache / "checkout" @property def mercurial_cache_sharebase(self): """ When local mercurial cache is enabled, path to the shared folder for robust checkout """ if self.mercurial_cache is None: return return self.mercurial_cache / "shared" def is_allowed_path(self, path): """ Is this path allowed for reporting ? """ return any([fnmatch.fnmatch(path, rule) for rule in self.allowed_paths]) def cleanup(self): shutil.rmtree(self.hgmo_cache) @property def taskcluster_url(self): """ Build the current taskcluster task url """ if self.taskcluster is None or self.taskcluster.local: return return f"https://firefox-ci-tc.services.mozilla.com/tasks/{self.taskcluster.task_id}" # Shared instance settings = Settings()