xref: /OpenGrok/tools/src/main/python/opengrok_tools/utils/mirror.py (revision cff95066cb05b52120fe4f3cd315b963de174813)
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