12d57dc69SVladimir Kotal#!/usr/bin/env python3 22d57dc69SVladimir Kotal# 32d57dc69SVladimir Kotal# CDDL HEADER START 42d57dc69SVladimir Kotal# 52d57dc69SVladimir Kotal# The contents of this file are subject to the terms of the 62d57dc69SVladimir Kotal# Common Development and Distribution License (the "License"). 72d57dc69SVladimir Kotal# You may not use this file except in compliance with the License. 82d57dc69SVladimir Kotal# 92d57dc69SVladimir Kotal# See LICENSE.txt included in this distribution for the specific 102d57dc69SVladimir Kotal# language governing permissions and limitations under the License. 112d57dc69SVladimir Kotal# 122d57dc69SVladimir Kotal# When distributing Covered Code, include this CDDL HEADER in each 132d57dc69SVladimir Kotal# file and include the License file at LICENSE.txt. 142d57dc69SVladimir Kotal# If applicable, add the following below this CDDL HEADER, with the 152d57dc69SVladimir Kotal# fields enclosed by brackets "[]" replaced with your own identifying 162d57dc69SVladimir Kotal# information: Portions Copyright [yyyy] [name of copyright owner] 172d57dc69SVladimir Kotal# 182d57dc69SVladimir Kotal# CDDL HEADER END 192d57dc69SVladimir Kotal# 202d57dc69SVladimir Kotal 212d57dc69SVladimir Kotal# 224f6348acSVladimir Kotal# Copyright (c) 2018, 2022, Oracle and/or its affiliates. All rights reserved. 232d57dc69SVladimir Kotal# 242d57dc69SVladimir Kotal 252d57dc69SVladimir Kotalimport re 262d57dc69SVladimir Kotalimport os 272d57dc69SVladimir Kotalimport fnmatch 282d57dc69SVladimir Kotalimport logging 292d57dc69SVladimir Kotalimport urllib 30ae5b3cb8SVladimir Kotalfrom tempfile import gettempdir 312d57dc69SVladimir Kotal 3296aeefc4SVladimir Kotalfrom requests.exceptions import RequestException 332d57dc69SVladimir Kotal 342d57dc69SVladimir Kotalfrom .exitvals import ( 352d57dc69SVladimir Kotal FAILURE_EXITVAL, 362d57dc69SVladimir Kotal CONTINUE_EXITVAL, 372d57dc69SVladimir Kotal SUCCESS_EXITVAL 382d57dc69SVladimir Kotal) 39c41895f8SVladimir Kotalfrom .patterns import PROJECT_SUBST, COMMAND_PROPERTY, CALL_PROPERTY 40c41895f8SVladimir Kotalfrom .utils import is_exe, check_create_dir, get_int, get_bool 41f321caadSVladimir Kotalfrom .opengrok import get_repos, get_repo_type, get_uri, delete_project_data 422d57dc69SVladimir Kotalfrom .hook import run_hook 432d57dc69SVladimir Kotalfrom .command import Command 44*cff95066SVladimir Kotalfrom .commandsequence import ApiCall 45*cff95066SVladimir Kotalfrom .restful import call_rest_api, do_api_call 462d57dc69SVladimir Kotal 47ae5b3cb8SVladimir Kotalfrom ..scm.repofactory import get_repository 482d57dc69SVladimir Kotalfrom ..scm.repository import RepositoryException 492d57dc69SVladimir Kotal 502d57dc69SVladimir Kotal 512d57dc69SVladimir Kotal# "constants" 522d57dc69SVladimir KotalHOOK_TIMEOUT_PROPERTY = 'hook_timeout' 532d57dc69SVladimir KotalCMD_TIMEOUT_PROPERTY = 'command_timeout' 542d57dc69SVladimir KotalIGNORED_REPOS_PROPERTY = 'ignored_repos' 552d57dc69SVladimir KotalPROXY_PROPERTY = 'proxy' 56941332aaSVladimir KotalINCOMING_PROPERTY = 'incoming_check' 573f3da1e6SVladimir KotalIGNORE_ERR_PROPERTY = 'ignore_errors' 582d57dc69SVladimir KotalCOMMANDS_PROPERTY = 'commands' 592d57dc69SVladimir KotalDISABLED_PROPERTY = 'disabled' 602d57dc69SVladimir KotalDISABLED_REASON_PROPERTY = 'disabled-reason' 612d57dc69SVladimir KotalHOOKDIR_PROPERTY = 'hookdir' 622d57dc69SVladimir KotalHOOKS_PROPERTY = 'hooks' 632d57dc69SVladimir KotalLOGDIR_PROPERTY = 'logdir' 642d57dc69SVladimir KotalPROJECTS_PROPERTY = 'projects' 652d57dc69SVladimir KotalDISABLED_CMD_PROPERTY = 'disabled_command' 66ea3064a1SVladimir KotalHOOK_PRE_PROPERTY = "pre" 67ea3064a1SVladimir KotalHOOK_POST_PROPERTY = "post" 68f321caadSVladimir KotalSTRIP_OUTGOING_PROPERTY = "strip_outgoing" 692d57dc69SVladimir Kotal 702d57dc69SVladimir Kotal 712d57dc69SVladimir Kotaldef get_repos_for_project(project_name, uri, source_root, 722d57dc69SVladimir Kotal ignored_repos=None, 7329e16ac4SVladimir Kotal commands=None, proxy=None, command_timeout=None, 74732be1c2SVladimir Kotal headers=None, timeout=None): 752d57dc69SVladimir Kotal """ 762d57dc69SVladimir Kotal :param project_name: project name 772d57dc69SVladimir Kotal :param uri: web application URI 782d57dc69SVladimir Kotal :param source_root source root 792d57dc69SVladimir Kotal :param ignored_repos: list of ignored repositories 802d57dc69SVladimir Kotal :param commands: dictionary of commands - paths to SCM programs 812d57dc69SVladimir Kotal :param proxy: dictionary of proxy servers - to be used as environment 822d57dc69SVladimir Kotal variables 832d57dc69SVladimir Kotal :param command_timeout: command timeout value in seconds 8429e16ac4SVladimir Kotal :param headers: optional HTTP headers dictionary 85959b34e9SVladimir Kotal :param timeout: connect timeout for API calls 86a094581dSVladimir Kotal :return: list of Repository objects, the repository matching the project path will be first 872d57dc69SVladimir Kotal """ 882d57dc69SVladimir Kotal 892d57dc69SVladimir Kotal logger = logging.getLogger(__name__) 902d57dc69SVladimir Kotal 912d57dc69SVladimir Kotal repos = [] 92732be1c2SVladimir Kotal for repo_path in get_repos(logger, project_name, uri, 93732be1c2SVladimir Kotal headers=headers, timeout=timeout): 942d57dc69SVladimir Kotal logger.debug("Repository path = {}".format(repo_path)) 952d57dc69SVladimir Kotal 962d57dc69SVladimir Kotal if ignored_repos: 972d57dc69SVladimir Kotal r_path = os.path.relpath(repo_path, '/' + project_name) 982d57dc69SVladimir Kotal if any(map(lambda repo: fnmatch.fnmatch(r_path, repo), 992d57dc69SVladimir Kotal ignored_repos)): 1002d57dc69SVladimir Kotal logger.info("repository {} ignored".format(repo_path)) 1012d57dc69SVladimir Kotal continue 1022d57dc69SVladimir Kotal 103732be1c2SVladimir Kotal repo_type = get_repo_type(logger, repo_path, uri, 104732be1c2SVladimir Kotal headers=headers, timeout=timeout) 1052d57dc69SVladimir Kotal if not repo_type: 1062d57dc69SVladimir Kotal raise RepositoryException("cannot determine type of repository {}". 1072d57dc69SVladimir Kotal format(repo_path)) 1082d57dc69SVladimir Kotal 1092d57dc69SVladimir Kotal logger.debug("Repository type = {}".format(repo_type)) 1102d57dc69SVladimir Kotal 1112d57dc69SVladimir Kotal repo = None 1122d57dc69SVladimir Kotal try: 1132d57dc69SVladimir Kotal # The OpenGrok convention is that the form of repo_path is absolute 1142d57dc69SVladimir Kotal # so joining the paths would actually spoil things. Hence, be 1152d57dc69SVladimir Kotal # careful. 1162d57dc69SVladimir Kotal if repo_path.startswith(os.path.sep): 1172d57dc69SVladimir Kotal path = source_root + repo_path 1182d57dc69SVladimir Kotal else: 1192d57dc69SVladimir Kotal path = os.path.join(source_root, repo_path) 1202d57dc69SVladimir Kotal 1212d57dc69SVladimir Kotal repo = get_repository(path, 1222d57dc69SVladimir Kotal repo_type, 1232d57dc69SVladimir Kotal project_name, 1242d57dc69SVladimir Kotal env=proxy, 1252d57dc69SVladimir Kotal timeout=command_timeout, 1262d57dc69SVladimir Kotal commands=commands) 1272d57dc69SVladimir Kotal except (RepositoryException, OSError) as e: 1282d57dc69SVladimir Kotal logger.error("Cannot get repository for {}: {}". 1292d57dc69SVladimir Kotal format(repo_path, e)) 1302d57dc69SVladimir Kotal 1312d57dc69SVladimir Kotal if repo: 132a094581dSVladimir Kotal if repo_path == os.path.sep + project_name: 133a094581dSVladimir Kotal repos.insert(0, repo) 134a094581dSVladimir Kotal else: 1352d57dc69SVladimir Kotal repos.append(repo) 1362d57dc69SVladimir Kotal 1372d57dc69SVladimir Kotal return repos 1382d57dc69SVladimir Kotal 1392d57dc69SVladimir Kotal 1402d57dc69SVladimir Kotaldef get_project_config(config, project_name): 1412d57dc69SVladimir Kotal """ 1422d57dc69SVladimir Kotal Return per project configuration, if any. 1432d57dc69SVladimir Kotal :param config: global configuration 1442d57dc69SVladimir Kotal :param project_name name of the project 1452d57dc69SVladimir Kotal :return: project configuration dictionary or None 1462d57dc69SVladimir Kotal """ 1472d57dc69SVladimir Kotal 1482d57dc69SVladimir Kotal logger = logging.getLogger(__name__) 1492d57dc69SVladimir Kotal 1502d57dc69SVladimir Kotal project_config = None 1512d57dc69SVladimir Kotal projects = config.get(PROJECTS_PROPERTY) 1522d57dc69SVladimir Kotal if projects: 1532d57dc69SVladimir Kotal project_config = projects.get(project_name) 1542d57dc69SVladimir Kotal if not project_config: 1552d57dc69SVladimir Kotal for project_pattern in projects.keys(): 1562d57dc69SVladimir Kotal if re.match(project_pattern, project_name): 1572d57dc69SVladimir Kotal logger.debug("Project '{}' matched pattern '{}'". 1582d57dc69SVladimir Kotal format(project_name, project_pattern)) 1592d57dc69SVladimir Kotal project_config = projects.get(project_pattern) 1602d57dc69SVladimir Kotal break 1612d57dc69SVladimir Kotal 1622d57dc69SVladimir Kotal return project_config 1632d57dc69SVladimir Kotal 1642d57dc69SVladimir Kotal 1652d57dc69SVladimir Kotaldef get_project_properties(project_config, project_name, hookdir): 1662d57dc69SVladimir Kotal """ 1672d57dc69SVladimir Kotal Get properties of project needed to perform mirroring. 1682d57dc69SVladimir Kotal :param project_config: project configuration dictionary 1692d57dc69SVladimir Kotal :param project_name: name of the project 1702d57dc69SVladimir Kotal :param hookdir: directory with hooks 1712d57dc69SVladimir Kotal :return: list of properties: prehook, posthook, hook_timeout, 17217937b37SVladimir Kotal command_timeout, use_proxy, ignored_repos, check_changes, strip_outgoing, ignore_errors 1732d57dc69SVladimir Kotal """ 1742d57dc69SVladimir Kotal 1752d57dc69SVladimir Kotal prehook = None 1762d57dc69SVladimir Kotal posthook = None 1772d57dc69SVladimir Kotal hook_timeout = None 1782d57dc69SVladimir Kotal command_timeout = None 1792d57dc69SVladimir Kotal use_proxy = False 1802d57dc69SVladimir Kotal ignored_repos = None 181941332aaSVladimir Kotal check_changes = None 18217937b37SVladimir Kotal strip_outgoing = None 1833f3da1e6SVladimir Kotal ignore_errors = None 1842d57dc69SVladimir Kotal 1852d57dc69SVladimir Kotal logger = logging.getLogger(__name__) 1862d57dc69SVladimir Kotal 1872d57dc69SVladimir Kotal if project_config: 1882d57dc69SVladimir Kotal logger.debug("Project '{}' has specific (non-default) config". 1892d57dc69SVladimir Kotal format(project_name)) 1902d57dc69SVladimir Kotal 1912d57dc69SVladimir Kotal project_command_timeout = get_int(logger, "command timeout for " 1922d57dc69SVladimir Kotal "project {}". 1932d57dc69SVladimir Kotal format(project_name), 1942d57dc69SVladimir Kotal project_config. 1952d57dc69SVladimir Kotal get(CMD_TIMEOUT_PROPERTY)) 1962d57dc69SVladimir Kotal if project_command_timeout: 1972d57dc69SVladimir Kotal command_timeout = project_command_timeout 1982d57dc69SVladimir Kotal logger.debug("Project command timeout = {}". 1992d57dc69SVladimir Kotal format(command_timeout)) 2002d57dc69SVladimir Kotal 2012d57dc69SVladimir Kotal project_hook_timeout = get_int(logger, "hook timeout for " 2022d57dc69SVladimir Kotal "project {}". 2032d57dc69SVladimir Kotal format(project_name), 2042d57dc69SVladimir Kotal project_config. 2052d57dc69SVladimir Kotal get(HOOK_TIMEOUT_PROPERTY)) 2062d57dc69SVladimir Kotal if project_hook_timeout: 2072d57dc69SVladimir Kotal hook_timeout = project_hook_timeout 2082d57dc69SVladimir Kotal logger.debug("Project hook timeout = {}". 2092d57dc69SVladimir Kotal format(hook_timeout)) 2102d57dc69SVladimir Kotal 2112d57dc69SVladimir Kotal ignored_repos = project_config.get(IGNORED_REPOS_PROPERTY) 2122d57dc69SVladimir Kotal if ignored_repos: 2132d57dc69SVladimir Kotal logger.debug("has ignored repositories: {}". 2142d57dc69SVladimir Kotal format(ignored_repos)) 2152d57dc69SVladimir Kotal 2162d57dc69SVladimir Kotal hooks = project_config.get(HOOKS_PROPERTY) 2172d57dc69SVladimir Kotal if hooks: 2182d57dc69SVladimir Kotal for hookname in hooks: 219ea3064a1SVladimir Kotal if hookname == HOOK_PRE_PROPERTY: 2202d57dc69SVladimir Kotal prehook = os.path.join(hookdir, hooks['pre']) 2212d57dc69SVladimir Kotal logger.debug("pre-hook = {}".format(prehook)) 222ea3064a1SVladimir Kotal elif hookname == HOOK_POST_PROPERTY: 2232d57dc69SVladimir Kotal posthook = os.path.join(hookdir, hooks['post']) 2242d57dc69SVladimir Kotal logger.debug("post-hook = {}".format(posthook)) 2252d57dc69SVladimir Kotal 2262d57dc69SVladimir Kotal if project_config.get(PROXY_PROPERTY): 2272d57dc69SVladimir Kotal logger.debug("will use proxy") 2282d57dc69SVladimir Kotal use_proxy = True 2292d57dc69SVladimir Kotal 23012429534SVladimir Kotal if project_config.get(INCOMING_PROPERTY) is not None: 231941332aaSVladimir Kotal check_changes = get_bool(logger, ("incoming check for project {}". 232941332aaSVladimir Kotal format(project_name)), 233941332aaSVladimir Kotal project_config.get(INCOMING_PROPERTY)) 234941332aaSVladimir Kotal logger.debug("incoming check = {}".format(check_changes)) 235941332aaSVladimir Kotal 236f321caadSVladimir Kotal if project_config.get(STRIP_OUTGOING_PROPERTY) is not None: 23717937b37SVladimir Kotal strip_outgoing = get_bool(logger, ("outgoing check for project {}". 238f321caadSVladimir Kotal format(project_name)), 239f321caadSVladimir Kotal project_config.get(STRIP_OUTGOING_PROPERTY)) 240f321caadSVladimir Kotal logger.debug("outgoing check = {}".format(check_changes)) 241f321caadSVladimir Kotal 2423f3da1e6SVladimir Kotal if project_config.get(IGNORE_ERR_PROPERTY) is not None: 2433f3da1e6SVladimir Kotal ignore_errors = get_bool(logger, ("ignore errors for project {}". 2443f3da1e6SVladimir Kotal format(project_name)), 2453f3da1e6SVladimir Kotal project_config.get(IGNORE_ERR_PROPERTY)) 2463f3da1e6SVladimir Kotal logger.debug("ignore errors = {}".format(check_changes)) 2473f3da1e6SVladimir Kotal 2482d57dc69SVladimir Kotal if not ignored_repos: 2492d57dc69SVladimir Kotal ignored_repos = [] 2502d57dc69SVladimir Kotal 2512d57dc69SVladimir Kotal return prehook, posthook, hook_timeout, command_timeout, \ 25217937b37SVladimir Kotal use_proxy, ignored_repos, check_changes, strip_outgoing, ignore_errors 2532d57dc69SVladimir Kotal 2542d57dc69SVladimir Kotal 2552d57dc69SVladimir Kotaldef process_hook(hook_ident, hook, source_root, project_name, proxy, 2562d57dc69SVladimir Kotal hook_timeout): 2572d57dc69SVladimir Kotal """ 2582d57dc69SVladimir Kotal :param hook_ident: ident of the hook to be used in log entries 2592d57dc69SVladimir Kotal :param hook: hook 2602d57dc69SVladimir Kotal :param source_root: source root path 2612d57dc69SVladimir Kotal :param project_name: project name 2622d57dc69SVladimir Kotal :param proxy: proxy or None 2632d57dc69SVladimir Kotal :param hook_timeout: hook run timeout 2642d57dc69SVladimir Kotal :return: False if hook failed, else True 2652d57dc69SVladimir Kotal """ 2662d57dc69SVladimir Kotal if hook: 2672d57dc69SVladimir Kotal logger = logging.getLogger(__name__) 2682d57dc69SVladimir Kotal 2692d57dc69SVladimir Kotal logger.info("Running {} hook".format(hook_ident)) 2702d57dc69SVladimir Kotal if run_hook(logger, hook, 2712d57dc69SVladimir Kotal os.path.join(source_root, project_name), proxy, 2722d57dc69SVladimir Kotal hook_timeout) != SUCCESS_EXITVAL: 2732d57dc69SVladimir Kotal logger.error("{} hook failed for project {}". 2742d57dc69SVladimir Kotal format(hook_ident, project_name)) 2752d57dc69SVladimir Kotal return False 2762d57dc69SVladimir Kotal 2772d57dc69SVladimir Kotal return True 2782d57dc69SVladimir Kotal 2792d57dc69SVladimir Kotal 280b66049deSVladimir Kotaldef process_changes(repos, project_name, uri, headers=None): 2812d57dc69SVladimir Kotal """ 2822d57dc69SVladimir Kotal :param repos: repository list 2832d57dc69SVladimir Kotal :param project_name: project name 284a094581dSVladimir Kotal :param uri: web application URI 285b66049deSVladimir Kotal :param headers: optional dictionary of HTTP headers 2862d57dc69SVladimir Kotal :return: exit code 2872d57dc69SVladimir Kotal """ 2882d57dc69SVladimir Kotal logger = logging.getLogger(__name__) 2892d57dc69SVladimir Kotal 2902d57dc69SVladimir Kotal changes_detected = False 2912d57dc69SVladimir Kotal 29212429534SVladimir Kotal logger.debug("Checking incoming changes for project {}".format(project_name)) 29312429534SVladimir Kotal 2942d57dc69SVladimir Kotal # check if the project is a new project - full index is necessary 2952d57dc69SVladimir Kotal try: 2962d57dc69SVladimir Kotal r = do_api_call('GET', get_uri(uri, 'api', 'v1', 'projects', 2972d57dc69SVladimir Kotal urllib.parse.quote_plus(project_name), 298b66049deSVladimir Kotal 'property', 'indexed'), 299b66049deSVladimir Kotal headers=headers) 3002d57dc69SVladimir Kotal if not bool(r.json()): 3012d57dc69SVladimir Kotal changes_detected = True 302e8578e62SVladimir Kotal logger.info('Project {} has not been indexed yet, ' 303e8578e62SVladimir Kotal 'overriding incoming check' 3042d57dc69SVladimir Kotal .format(project_name)) 3052d57dc69SVladimir Kotal except ValueError as e: 3062d57dc69SVladimir Kotal logger.error('Unable to parse project \'{}\' indexed flag: {}' 3072d57dc69SVladimir Kotal .format(project_name, e)) 3082d57dc69SVladimir Kotal return FAILURE_EXITVAL 30996aeefc4SVladimir Kotal except RequestException as e: 3102d57dc69SVladimir Kotal logger.error('Unable to determine project \'{}\' indexed flag: {}' 3112d57dc69SVladimir Kotal .format(project_name, e)) 3122d57dc69SVladimir Kotal return FAILURE_EXITVAL 3132d57dc69SVladimir Kotal 3142d57dc69SVladimir Kotal # check if the project has any new changes in the SCM 3152d57dc69SVladimir Kotal if not changes_detected: 3162d57dc69SVladimir Kotal for repo in repos: 3172d57dc69SVladimir Kotal try: 3182d57dc69SVladimir Kotal if repo.incoming(): 3192d57dc69SVladimir Kotal logger.debug('Repository {} has incoming changes'. 3202d57dc69SVladimir Kotal format(repo)) 3212d57dc69SVladimir Kotal changes_detected = True 3222d57dc69SVladimir Kotal break 323a094581dSVladimir Kotal 324a094581dSVladimir Kotal if repo.top_level(): 325a094581dSVladimir Kotal logger.debug('Repository {} is top level, finishing incoming check'. 326a094581dSVladimir Kotal format(repo)) 327a094581dSVladimir Kotal break 3282d57dc69SVladimir Kotal except RepositoryException: 3292d57dc69SVladimir Kotal logger.error('Cannot determine incoming changes for ' 3302d57dc69SVladimir Kotal 'repository {}'.format(repo)) 3312d57dc69SVladimir Kotal return FAILURE_EXITVAL 3322d57dc69SVladimir Kotal 3332d57dc69SVladimir Kotal if not changes_detected: 3342d57dc69SVladimir Kotal logger.info('No incoming changes for repositories in ' 3352d57dc69SVladimir Kotal 'project {}'. 3362d57dc69SVladimir Kotal format(project_name)) 3372d57dc69SVladimir Kotal return CONTINUE_EXITVAL 3382d57dc69SVladimir Kotal 3392d57dc69SVladimir Kotal return SUCCESS_EXITVAL 3402d57dc69SVladimir Kotal 3412d57dc69SVladimir Kotal 3422d57dc69SVladimir Kotaldef run_command(cmd, project_name): 3432d57dc69SVladimir Kotal cmd.execute() 3442d57dc69SVladimir Kotal if cmd.getretcode() != 0: 3452d57dc69SVladimir Kotal logger = logging.getLogger(__name__) 3462d57dc69SVladimir Kotal 3472d57dc69SVladimir Kotal logger.error("Command for disabled project '{}' failed " 3482d57dc69SVladimir Kotal "with error code {}: {}". 3492d57dc69SVladimir Kotal format(project_name, cmd.getretcode(), 3502d57dc69SVladimir Kotal cmd.getoutputstr())) 3512d57dc69SVladimir Kotal 3522d57dc69SVladimir Kotal 353b0fe77bdSVladimir Kotaldef handle_disabled_project(config, project_name, disabled_msg, headers=None, 354f321caadSVladimir Kotal timeout=None, api_timeout=None): 355*cff95066SVladimir Kotal 3562d57dc69SVladimir Kotal disabled_command = config.get(DISABLED_CMD_PROPERTY) 3572d57dc69SVladimir Kotal if disabled_command: 3582d57dc69SVladimir Kotal logger = logging.getLogger(__name__) 3592d57dc69SVladimir Kotal 360c41895f8SVladimir Kotal if disabled_command.get(CALL_PROPERTY): 361c41895f8SVladimir Kotal call = disabled_command.get(CALL_PROPERTY) 362*cff95066SVladimir Kotal api_call = ApiCall(call) 363c41895f8SVladimir Kotal 364*cff95066SVladimir Kotal text = None 365*cff95066SVladimir Kotal data = api_call.data 3662d57dc69SVladimir Kotal if type(data) is dict: 3672d57dc69SVladimir Kotal text = data.get("text") 368c41895f8SVladimir Kotal 369c41895f8SVladimir Kotal # Is this perhaps OpenGrok API call to supply a Message for the UI ? 370c41895f8SVladimir Kotal # If so and there was a string supplied, append it to the message text. 371*cff95066SVladimir Kotal if text and api_call.uri.find("/api/v1/") > 0 and type(disabled_msg) is str: 3722d57dc69SVladimir Kotal logger.debug("Appending text to message: {}". 3732d57dc69SVladimir Kotal format(disabled_msg)) 374*cff95066SVladimir Kotal api_call.data["text"] = text + ": " + disabled_msg 3752d57dc69SVladimir Kotal 3762d57dc69SVladimir Kotal try: 377*cff95066SVladimir Kotal call_rest_api(api_call, {PROJECT_SUBST: project_name}, 378f321caadSVladimir Kotal http_headers=headers, timeout=timeout, api_timeout=api_timeout) 37996aeefc4SVladimir Kotal except RequestException as e: 3802d57dc69SVladimir Kotal logger.error("API call failed for disabled command of " 3812d57dc69SVladimir Kotal "project '{}': {}". 3822d57dc69SVladimir Kotal format(project_name, e)) 383c41895f8SVladimir Kotal elif disabled_command.get(COMMAND_PROPERTY): 384c41895f8SVladimir Kotal command_args = disabled_command.get(COMMAND_PROPERTY) 3852d57dc69SVladimir Kotal args = [project_name] 3862d57dc69SVladimir Kotal if disabled_msg and type(disabled_msg) is str: 3872d57dc69SVladimir Kotal args.append(disabled_command) 3882d57dc69SVladimir Kotal 3892d57dc69SVladimir Kotal cmd = Command(command_args, 3902d57dc69SVladimir Kotal env_vars=disabled_command.get("env"), 3912d57dc69SVladimir Kotal resource_limits=disabled_command.get("limits"), 3922d57dc69SVladimir Kotal args_subst={PROJECT_SUBST: project_name}, 3932d57dc69SVladimir Kotal args_append=args, excl_subst=True) 3942d57dc69SVladimir Kotal run_command(cmd, project_name) 395c41895f8SVladimir Kotal else: 396c41895f8SVladimir Kotal raise Exception(f"unknown disabled action: {disabled_command}") 3972d57dc69SVladimir Kotal 3982d57dc69SVladimir Kotal 3993f3da1e6SVladimir Kotaldef get_mirror_retcode(ignore_errors, value): 40038643059SVladimir Kotal if ignore_errors and value != SUCCESS_EXITVAL: 4013f3da1e6SVladimir Kotal logger = logging.getLogger(__name__) 40268ad6df7SVladimir Kotal logger.info("error code is {} however '{}' property is true, " 40368ad6df7SVladimir Kotal "so returning success".format(value, IGNORE_ERR_PROPERTY)) 4043f3da1e6SVladimir Kotal return SUCCESS_EXITVAL 4053f3da1e6SVladimir Kotal 4063f3da1e6SVladimir Kotal return value 4073f3da1e6SVladimir Kotal 4083f3da1e6SVladimir Kotal 409f321caadSVladimir Kotaldef process_outgoing(repos, project_name): 410f321caadSVladimir Kotal """ 411f321caadSVladimir Kotal Detect and strip any outgoing changes for the repositories. 412f321caadSVladimir Kotal :param repos: list of repository objects 413f321caadSVladimir Kotal :param project_name: name of the project 414f321caadSVladimir Kotal :return: if any of the repositories had to be reset 415f321caadSVladimir Kotal """ 416f321caadSVladimir Kotal 417f321caadSVladimir Kotal logger = logging.getLogger(__name__) 41837a85de5SVladimir Kotal logger.info("Checking outgoing changes for project {}".format(project_name)) 419f321caadSVladimir Kotal 420f321caadSVladimir Kotal ret = False 421f321caadSVladimir Kotal for repo in repos: 42211f0562aSVladimir Kotal logger.debug("Checking outgoing changes for repository {}".format(repo)) 423f321caadSVladimir Kotal if repo.strip_outgoing(): 424f321caadSVladimir Kotal logger.debug('Repository {} in project {} had outgoing changes stripped'. 425f321caadSVladimir Kotal format(repo, project_name)) 426f321caadSVladimir Kotal ret = True 427f321caadSVladimir Kotal 42829732345SVladimir Kotal if repo.top_level(): 42929732345SVladimir Kotal logger.debug('Repository {} is top level, finishing outgoing changeset handling'. 43029732345SVladimir Kotal format(repo)) 43129732345SVladimir Kotal break 43229732345SVladimir Kotal 433f321caadSVladimir Kotal return ret 434f321caadSVladimir Kotal 435f321caadSVladimir Kotal 43617937b37SVladimir Kotaldef mirror_project(config, project_name, check_changes, strip_outgoing, uri, 437f321caadSVladimir Kotal source_root, headers=None, timeout=None, api_timeout=None): 4382d57dc69SVladimir Kotal """ 4392d57dc69SVladimir Kotal Mirror the repositories of single project. 4402d57dc69SVladimir Kotal :param config global configuration dictionary 4412d57dc69SVladimir Kotal :param project_name: name of the project 4422d57dc69SVladimir Kotal :param check_changes: check for changes in the project or its repositories 4432d57dc69SVladimir Kotal and terminate if no change is found 44417937b37SVladimir Kotal :param strip_outgoing: check for outgoing changes in the repositories of the project, 445f321caadSVladimir Kotal strip the changes and wipe project data if such changes were found 446a094581dSVladimir Kotal :param uri web application URI 447a094581dSVladimir Kotal :param source_root source root 448732be1c2SVladimir Kotal :param headers: optional dictionary of HTTP headers 449f321caadSVladimir Kotal :param timeout: connect timeout 450f321caadSVladimir Kotal :param api_timeout: optional timeout in seconds for API call response 4512d57dc69SVladimir Kotal :return exit code 4522d57dc69SVladimir Kotal """ 4532d57dc69SVladimir Kotal 4542d57dc69SVladimir Kotal ret = SUCCESS_EXITVAL 4552d57dc69SVladimir Kotal 4562d57dc69SVladimir Kotal logger = logging.getLogger(__name__) 4572d57dc69SVladimir Kotal 4582d57dc69SVladimir Kotal project_config = get_project_config(config, project_name) 4592d57dc69SVladimir Kotal prehook, posthook, hook_timeout, command_timeout, use_proxy, \ 460941332aaSVladimir Kotal ignored_repos, \ 4613f3da1e6SVladimir Kotal check_changes_proj, \ 46217937b37SVladimir Kotal strip_outgoing_proj, \ 4633f3da1e6SVladimir Kotal ignore_errors_proj = get_project_properties(project_config, 4642d57dc69SVladimir Kotal project_name, 465b0fe77bdSVladimir Kotal config. 466b0fe77bdSVladimir Kotal get(HOOKDIR_PROPERTY)) 4672d57dc69SVladimir Kotal 4682d57dc69SVladimir Kotal if not command_timeout: 4692d57dc69SVladimir Kotal command_timeout = config.get(CMD_TIMEOUT_PROPERTY) 4702d57dc69SVladimir Kotal if not hook_timeout: 4712d57dc69SVladimir Kotal hook_timeout = config.get(HOOK_TIMEOUT_PROPERTY) 4722d57dc69SVladimir Kotal 473b0fe77bdSVladimir Kotal if check_changes_proj is None: 474b0fe77bdSVladimir Kotal check_changes_config = config.get(INCOMING_PROPERTY) 475b0fe77bdSVladimir Kotal else: 476b0fe77bdSVladimir Kotal check_changes_config = check_changes_proj 477b0fe77bdSVladimir Kotal 47817937b37SVladimir Kotal if strip_outgoing_proj is None: 47917937b37SVladimir Kotal strip_outgoing_config = config.get(STRIP_OUTGOING_PROPERTY) 480f321caadSVladimir Kotal else: 48117937b37SVladimir Kotal strip_outgoing_config = strip_outgoing_proj 482f321caadSVladimir Kotal 4833f3da1e6SVladimir Kotal if ignore_errors_proj is None: 4843f3da1e6SVladimir Kotal ignore_errors = config.get(IGNORE_ERR_PROPERTY) 4853f3da1e6SVladimir Kotal else: 4863f3da1e6SVladimir Kotal ignore_errors = ignore_errors_proj 4873f3da1e6SVladimir Kotal 4882d57dc69SVladimir Kotal proxy = None 4892d57dc69SVladimir Kotal if use_proxy: 4902d57dc69SVladimir Kotal proxy = config.get(PROXY_PROPERTY) 4912d57dc69SVladimir Kotal 4922d57dc69SVladimir Kotal # We want this to be logged to the log file (if any). 493941332aaSVladimir Kotal if project_config and project_config.get(DISABLED_PROPERTY): 4942d57dc69SVladimir Kotal handle_disabled_project(config, project_name, 4952d57dc69SVladimir Kotal project_config. 496eba5f710SVladimir Kotal get(DISABLED_REASON_PROPERTY), 497732be1c2SVladimir Kotal headers=headers, 498f321caadSVladimir Kotal timeout=timeout, 499f321caadSVladimir Kotal api_timeout=api_timeout) 5002d57dc69SVladimir Kotal logger.info("Project '{}' disabled, exiting". 5012d57dc69SVladimir Kotal format(project_name)) 5022d57dc69SVladimir Kotal return CONTINUE_EXITVAL 5032d57dc69SVladimir Kotal 5042d57dc69SVladimir Kotal # 5052d57dc69SVladimir Kotal # Cache the repositories first. This way it will be known that 5062d57dc69SVladimir Kotal # something is not right, avoiding any needless pre-hook run. 5072d57dc69SVladimir Kotal # 5082d57dc69SVladimir Kotal repos = get_repos_for_project(project_name, 5092d57dc69SVladimir Kotal uri, 5102d57dc69SVladimir Kotal source_root, 5112d57dc69SVladimir Kotal ignored_repos=ignored_repos, 5122d57dc69SVladimir Kotal commands=config. 5132d57dc69SVladimir Kotal get(COMMANDS_PROPERTY), 5142d57dc69SVladimir Kotal proxy=proxy, 51529e16ac4SVladimir Kotal command_timeout=command_timeout, 516732be1c2SVladimir Kotal headers=headers, 517732be1c2SVladimir Kotal timeout=timeout) 5182d57dc69SVladimir Kotal if not repos: 5192d57dc69SVladimir Kotal logger.info("No repositories for project {}". 5202d57dc69SVladimir Kotal format(project_name)) 5212d57dc69SVladimir Kotal return CONTINUE_EXITVAL 5222d57dc69SVladimir Kotal 523b0fe77bdSVladimir Kotal if check_changes_config is not None: 524b0fe77bdSVladimir Kotal check_changes = check_changes_config 525941332aaSVladimir Kotal 52617937b37SVladimir Kotal if strip_outgoing_config is not None: 52717937b37SVladimir Kotal strip_outgoing = strip_outgoing_config 528f321caadSVladimir Kotal 52938bc7254SVladimir Kotal # Check outgoing changes first. If there are any, such changes will be stripped 53038bc7254SVladimir Kotal # and the subsequent incoming check will do the right thing. 53117937b37SVladimir Kotal if strip_outgoing: 532f321caadSVladimir Kotal try: 533f321caadSVladimir Kotal r = process_outgoing(repos, project_name) 534f321caadSVladimir Kotal except RepositoryException as exc: 535f321caadSVladimir Kotal logger.error('Failed to handle outgoing changes for ' 536f321caadSVladimir Kotal 'a repository in project {}: {}'.format(project_name, exc)) 537f321caadSVladimir Kotal return get_mirror_retcode(ignore_errors, FAILURE_EXITVAL) 538f321caadSVladimir Kotal if r: 53907e3292dSVladimir Kotal logger.info("Found outgoing changesets, removing data for project {}". 54007e3292dSVladimir Kotal format(project_name)) 54137a85de5SVladimir Kotal r = delete_project_data(logger, project_name, uri, 54237a85de5SVladimir Kotal headers=headers, timeout=timeout, api_timeout=api_timeout) 54337a85de5SVladimir Kotal if not r: 54437a85de5SVladimir Kotal return get_mirror_retcode(ignore_errors, FAILURE_EXITVAL) 545f321caadSVladimir Kotal 5462d57dc69SVladimir Kotal # Check if the project or any of its repositories have changed. 5472d57dc69SVladimir Kotal if check_changes: 548b66049deSVladimir Kotal r = process_changes(repos, project_name, uri, headers=headers) 5492d57dc69SVladimir Kotal if r != SUCCESS_EXITVAL: 5503f3da1e6SVladimir Kotal return get_mirror_retcode(ignore_errors, r) 5512d57dc69SVladimir Kotal 552ea3064a1SVladimir Kotal if not process_hook(HOOK_PRE_PROPERTY, prehook, source_root, project_name, 553ea3064a1SVladimir Kotal proxy, hook_timeout): 5543f3da1e6SVladimir Kotal return get_mirror_retcode(ignore_errors, FAILURE_EXITVAL) 5552d57dc69SVladimir Kotal 5562d57dc69SVladimir Kotal # 5572d57dc69SVladimir Kotal # If one of the repositories fails to sync, the whole project sync 5582d57dc69SVladimir Kotal # is treated as failed, i.e. the program will return FAILURE_EXITVAL. 5592d57dc69SVladimir Kotal # 5602d57dc69SVladimir Kotal for repo in repos: 5617888fba5SVladimir Kotal logger.info("Synchronizing repository {}".format(repo.path)) 5622d57dc69SVladimir Kotal if repo.sync() != SUCCESS_EXITVAL: 5632d57dc69SVladimir Kotal logger.error("failed to synchronize repository {}". 5642d57dc69SVladimir Kotal format(repo.path)) 5652d57dc69SVladimir Kotal ret = FAILURE_EXITVAL 5662d57dc69SVladimir Kotal 567a094581dSVladimir Kotal if repo.top_level(): 568fed851daSVladimir Kotal logger.debug("Repository {} is top level, breaking".format(repo)) 569a094581dSVladimir Kotal break 570a094581dSVladimir Kotal 571ea3064a1SVladimir Kotal if not process_hook(HOOK_POST_PROPERTY, posthook, source_root, project_name, 572ea3064a1SVladimir Kotal proxy, hook_timeout): 5733f3da1e6SVladimir Kotal return get_mirror_retcode(ignore_errors, FAILURE_EXITVAL) 5742d57dc69SVladimir Kotal 5753f3da1e6SVladimir Kotal return get_mirror_retcode(ignore_errors, ret) 5762d57dc69SVladimir Kotal 5772d57dc69SVladimir Kotal 5782d57dc69SVladimir Kotaldef check_project_configuration(multiple_project_config, hookdir=False, 5792d57dc69SVladimir Kotal proxy=False): 5802d57dc69SVladimir Kotal """ 5812d57dc69SVladimir Kotal Check configuration of given project 5822d57dc69SVladimir Kotal :param multiple_project_config: project configuration dictionary 5832d57dc69SVladimir Kotal :param hookdir: hook directory 5842d57dc69SVladimir Kotal :param proxy: proxy setting 5852d57dc69SVladimir Kotal :return: True if the configuration checks out, False otherwise 5862d57dc69SVladimir Kotal """ 5872d57dc69SVladimir Kotal 5882d57dc69SVladimir Kotal logger = logging.getLogger(__name__) 5892d57dc69SVladimir Kotal 5902d57dc69SVladimir Kotal # Quick sanity check. 5912d57dc69SVladimir Kotal known_project_tunables = [DISABLED_PROPERTY, CMD_TIMEOUT_PROPERTY, 5922d57dc69SVladimir Kotal HOOK_TIMEOUT_PROPERTY, PROXY_PROPERTY, 5932d57dc69SVladimir Kotal IGNORED_REPOS_PROPERTY, HOOKS_PROPERTY, 5943f3da1e6SVladimir Kotal DISABLED_REASON_PROPERTY, INCOMING_PROPERTY, 595f321caadSVladimir Kotal IGNORE_ERR_PROPERTY, STRIP_OUTGOING_PROPERTY] 5962d57dc69SVladimir Kotal 5972d57dc69SVladimir Kotal if not multiple_project_config: 5982d57dc69SVladimir Kotal return True 5992d57dc69SVladimir Kotal 6002d57dc69SVladimir Kotal logger.debug("Checking project configuration") 6012d57dc69SVladimir Kotal 6022d57dc69SVladimir Kotal for project_name, project_config in multiple_project_config.items(): 6032d57dc69SVladimir Kotal logger.debug("Checking configuration of project {}". 6042d57dc69SVladimir Kotal format(project_name)) 6052d57dc69SVladimir Kotal 6062d57dc69SVladimir Kotal if project_config is None: 6072d57dc69SVladimir Kotal logger.warning("Project {} has empty configuration". 6082d57dc69SVladimir Kotal format(project_name)) 6092d57dc69SVladimir Kotal continue 6102d57dc69SVladimir Kotal 6112d57dc69SVladimir Kotal diff = set(project_config.keys()).difference(known_project_tunables) 6122d57dc69SVladimir Kotal if diff: 6132d57dc69SVladimir Kotal logger.error("unknown project configuration option(s) '{}' " 6142d57dc69SVladimir Kotal "for project {}".format(diff, project_name)) 6152d57dc69SVladimir Kotal return False 6162d57dc69SVladimir Kotal 6172d57dc69SVladimir Kotal if project_config.get(PROXY_PROPERTY) and not proxy: 6182d57dc69SVladimir Kotal logger.error("global proxy setting is needed in order to" 6192d57dc69SVladimir Kotal "have per-project proxy") 6202d57dc69SVladimir Kotal return False 6212d57dc69SVladimir Kotal 6222d57dc69SVladimir Kotal hooks = project_config.get(HOOKS_PROPERTY) 6232d57dc69SVladimir Kotal if hooks: 6242d57dc69SVladimir Kotal if not hookdir: 6252d57dc69SVladimir Kotal logger.error("Need to have '{}' in the configuration " 6262d57dc69SVladimir Kotal "to run hooks".format(HOOKDIR_PROPERTY)) 6272d57dc69SVladimir Kotal return False 6282d57dc69SVladimir Kotal 6292d57dc69SVladimir Kotal if not os.path.isdir(hookdir): 6302d57dc69SVladimir Kotal logger.error("Not a directory: {}".format(hookdir)) 6312d57dc69SVladimir Kotal return False 6322d57dc69SVladimir Kotal 6332d57dc69SVladimir Kotal for hookname in hooks.keys(): 6342d57dc69SVladimir Kotal if hookname not in ["pre", "post"]: 6352d57dc69SVladimir Kotal logger.error("Unknown hook name '{}' for project '{}'". 6362d57dc69SVladimir Kotal format(hookname, project_name)) 6372d57dc69SVladimir Kotal return False 6382d57dc69SVladimir Kotal 6392d57dc69SVladimir Kotal hookpath = os.path.join(hookdir, hooks.get(hookname)) 6402d57dc69SVladimir Kotal if not is_exe(hookpath): 6412d57dc69SVladimir Kotal logger.error("hook file {} for project '{}' does not exist" 6422d57dc69SVladimir Kotal " or not executable". 6432d57dc69SVladimir Kotal format(hookpath, project_name)) 6442d57dc69SVladimir Kotal return False 6452d57dc69SVladimir Kotal 6462d57dc69SVladimir Kotal ignored_repos = project_config.get(IGNORED_REPOS_PROPERTY) 6472d57dc69SVladimir Kotal if ignored_repos: 6482d57dc69SVladimir Kotal if not isinstance(ignored_repos, list): 6492d57dc69SVladimir Kotal logger.error("{} for project {} is not a list". 6502d57dc69SVladimir Kotal format(IGNORED_REPOS_PROPERTY, project_name)) 6512d57dc69SVladimir Kotal return False 6522d57dc69SVladimir Kotal 6532d57dc69SVladimir Kotal try: 6542d57dc69SVladimir Kotal re.compile(project_name) 6552d57dc69SVladimir Kotal except re.error: 6562d57dc69SVladimir Kotal logger.error("Not a valid regular expression: {}". 6572d57dc69SVladimir Kotal format(project_name)) 6582d57dc69SVladimir Kotal return False 6592d57dc69SVladimir Kotal 6602d57dc69SVladimir Kotal return True 6612d57dc69SVladimir Kotal 6622d57dc69SVladimir Kotal 663009ba170SVladimir Kotaldef check_commands(commands): 664009ba170SVladimir Kotal """ 665009ba170SVladimir Kotal Validate the commands section of the configuration that allows 666009ba170SVladimir Kotal to override files used for SCM synchronization and incoming check. 667009ba170SVladimir Kotal This should be simple dictionary of string values. 668009ba170SVladimir Kotal :return: True if valid, False otherwise 669009ba170SVladimir Kotal """ 670009ba170SVladimir Kotal 671009ba170SVladimir Kotal logger = logging.getLogger(__name__) 672009ba170SVladimir Kotal 673009ba170SVladimir Kotal if commands is None: 674009ba170SVladimir Kotal return True 675009ba170SVladimir Kotal 676009ba170SVladimir Kotal if type(commands) is not dict: 677009ba170SVladimir Kotal logger.error("commands sections is not a dictionary") 678009ba170SVladimir Kotal return False 679009ba170SVladimir Kotal 680009ba170SVladimir Kotal for name, value in commands.items(): 681ae5b3cb8SVladimir Kotal try: 682ae5b3cb8SVladimir Kotal repo = get_repository(gettempdir(), name, "dummy_project", 683ae5b3cb8SVladimir Kotal commands=commands) 684ae5b3cb8SVladimir Kotal except RepositoryException as e: 685ae5b3cb8SVladimir Kotal logger.error("failed to check repository for '{}': '{}'". 686ae5b3cb8SVladimir Kotal format(name, e)) 687009ba170SVladimir Kotal return False 688009ba170SVladimir Kotal 689ae5b3cb8SVladimir Kotal if repo is None: 690ae5b3cb8SVladimir Kotal logger.error("unknown repository type '{}' under '{}': '{}'". 69142ee5e21SVladimir Kotal format(name, COMMANDS_PROPERTY, value)) 692009ba170SVladimir Kotal return False 693009ba170SVladimir Kotal 694ae5b3cb8SVladimir Kotal if not repo.check_command(): 695009ba170SVladimir Kotal return False 696009ba170SVladimir Kotal 697009ba170SVladimir Kotal return True 698009ba170SVladimir Kotal 699009ba170SVladimir Kotal 7002d57dc69SVladimir Kotaldef check_configuration(config): 7012d57dc69SVladimir Kotal """ 7022d57dc69SVladimir Kotal Validate configuration 7032d57dc69SVladimir Kotal :param config: global configuration dictionary 7042d57dc69SVladimir Kotal :return: True if valid, False otherwise 7052d57dc69SVladimir Kotal """ 7062d57dc69SVladimir Kotal 7072d57dc69SVladimir Kotal logger = logging.getLogger(__name__) 7082d57dc69SVladimir Kotal 7092d57dc69SVladimir Kotal global_tunables = [HOOKDIR_PROPERTY, PROXY_PROPERTY, LOGDIR_PROPERTY, 7102d57dc69SVladimir Kotal COMMANDS_PROPERTY, PROJECTS_PROPERTY, 7112d57dc69SVladimir Kotal HOOK_TIMEOUT_PROPERTY, CMD_TIMEOUT_PROPERTY, 7123f3da1e6SVladimir Kotal DISABLED_CMD_PROPERTY, INCOMING_PROPERTY, 713f321caadSVladimir Kotal IGNORE_ERR_PROPERTY, STRIP_OUTGOING_PROPERTY] 7143f3da1e6SVladimir Kotal 7152d57dc69SVladimir Kotal diff = set(config.keys()).difference(global_tunables) 7162d57dc69SVladimir Kotal if diff: 7172d57dc69SVladimir Kotal logger.error("unknown global configuration option(s): '{}'" 7182d57dc69SVladimir Kotal .format(diff)) 7192d57dc69SVladimir Kotal return False 7202d57dc69SVladimir Kotal 7212d57dc69SVladimir Kotal # Make sure the log directory exists. 7222d57dc69SVladimir Kotal logdir = config.get(LOGDIR_PROPERTY) 7232d57dc69SVladimir Kotal if logdir: 7242d57dc69SVladimir Kotal check_create_dir(logger, logdir) 7252d57dc69SVladimir Kotal 7262d57dc69SVladimir Kotal disabled_command = config.get(DISABLED_CMD_PROPERTY) 7272d57dc69SVladimir Kotal if disabled_command: 7282d57dc69SVladimir Kotal logger.debug("Disabled command: {}".format(disabled_command)) 7292d57dc69SVladimir Kotal 730009ba170SVladimir Kotal if not check_commands(config.get(COMMANDS_PROPERTY)): 731009ba170SVladimir Kotal return False 732009ba170SVladimir Kotal 7332d57dc69SVladimir Kotal if not check_project_configuration(config.get(PROJECTS_PROPERTY), 7342d57dc69SVladimir Kotal config.get(HOOKDIR_PROPERTY), 7352d57dc69SVladimir Kotal config.get(PROXY_PROPERTY)): 7362d57dc69SVladimir Kotal return False 7372d57dc69SVladimir Kotal 7382d57dc69SVladimir Kotal return True 739