1b2d29daeSVladimir Kotal#!/usr/bin/env python3 2b2d29daeSVladimir Kotal 3b2d29daeSVladimir Kotal# CDDL HEADER START 4b2d29daeSVladimir Kotal# 5b2d29daeSVladimir Kotal# The contents of this file are subject to the terms of the 6b2d29daeSVladimir Kotal# Common Development and Distribution License (the "License"). 7b2d29daeSVladimir Kotal# You may not use this file except in compliance with the License. 8b2d29daeSVladimir Kotal# 9b2d29daeSVladimir Kotal# See LICENSE.txt included in this distribution for the specific 10b2d29daeSVladimir Kotal# language governing permissions and limitations under the License. 11b2d29daeSVladimir Kotal# 12b2d29daeSVladimir Kotal# When distributing Covered Code, include this CDDL HEADER in each 13b2d29daeSVladimir Kotal# file and include the License file at LICENSE.txt. 14b2d29daeSVladimir Kotal# If applicable, add the following below this CDDL HEADER, with the 15b2d29daeSVladimir Kotal# fields enclosed by brackets "[]" replaced with your own identifying 16b2d29daeSVladimir Kotal# information: Portions Copyright [yyyy] [name of copyright owner] 17b2d29daeSVladimir Kotal# 18b2d29daeSVladimir Kotal# CDDL HEADER END 19b2d29daeSVladimir Kotal 20b2d29daeSVladimir Kotal# 21b2d29daeSVladimir Kotal# Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved. 22b2d29daeSVladimir Kotal# 23b2d29daeSVladimir Kotal 24b2d29daeSVladimir Kotalimport os 25b2d29daeSVladimir Kotalimport logging 26b2d29daeSVladimir Kotalimport multiprocessing 27e6daae6cSVladimir Kotalimport signal 28b2d29daeSVladimir Kotalimport shutil 29b2d29daeSVladimir Kotalimport subprocess 306100daaaSVladimir Kotalimport sys 31b9361335SVladimir Kotalimport tempfile 32b2d29daeSVladimir Kotalimport threading 33b2d29daeSVladimir Kotalimport time 34d44cbea0SVladimir Kotalfrom pathlib import Path 35d44cbea0SVladimir Kotalfrom requests import get, ConnectionError 36f1835fddSVladimir Kotalfrom flask import Flask 37f1835fddSVladimir Kotalfrom flask_httpauth import HTTPTokenAuth 38f1835fddSVladimir Kotalfrom waitress import serve 39b2d29daeSVladimir Kotal 40b2d29daeSVladimir Kotalfrom opengrok_tools.utils.log import get_console_logger, \ 41b2d29daeSVladimir Kotal get_log_level, get_class_basename 42b2d29daeSVladimir Kotalfrom opengrok_tools.deploy import deploy_war 43b2d29daeSVladimir Kotalfrom opengrok_tools.utils.indexer import Indexer 44b2d29daeSVladimir Kotalfrom opengrok_tools.sync import do_sync 45b9361335SVladimir Kotalfrom opengrok_tools.config_merge import merge_config_files 46b2d29daeSVladimir Kotalfrom opengrok_tools.utils.opengrok import list_projects, \ 4719f6e235SVladimir Kotal add_project, delete_project, get_configuration 48b2d29daeSVladimir Kotalfrom opengrok_tools.utils.readconfig import read_config 49b2d29daeSVladimir Kotalfrom opengrok_tools.utils.exitvals import SUCCESS_EXITVAL 505036d900SVladimir Kotalfrom opengrok_tools.utils.mirror import check_configuration 51b2d29daeSVladimir Kotal 52b2d29daeSVladimir Kotal 53b2d29daeSVladimir Kotalfs_root = os.path.abspath('.').split(os.path.sep)[0] + os.path.sep 54b2d29daeSVladimir Kotalif os.environ.get('OPENGROK_TOMCAT_ROOT'): # debug only 55b2d29daeSVladimir Kotal tomcat_root = os.environ.get('OPENGROK_TOMCAT_ROOT') 56b2d29daeSVladimir Kotalelse: 57b2d29daeSVladimir Kotal tomcat_root = os.path.join(fs_root, "usr", "local", "tomcat") 58b2d29daeSVladimir Kotal 59b2d29daeSVladimir Kotalif os.environ.get('OPENGROK_ROOT'): # debug only 60b2d29daeSVladimir Kotal OPENGROK_BASE_DIR = os.environ.get('OPENGROK_ROOT') 61b2d29daeSVladimir Kotalelse: 62b2d29daeSVladimir Kotal OPENGROK_BASE_DIR = os.path.join(fs_root, "opengrok") 63b2d29daeSVladimir Kotal 64b2d29daeSVladimir KotalOPENGROK_LIB_DIR = os.path.join(OPENGROK_BASE_DIR, "lib") 65b2d29daeSVladimir KotalOPENGROK_DATA_ROOT = os.path.join(OPENGROK_BASE_DIR, "data") 66b2d29daeSVladimir KotalOPENGROK_SRC_ROOT = os.path.join(OPENGROK_BASE_DIR, "src") 67b2d29daeSVladimir KotalBODY_INCLUDE_FILE = os.path.join(OPENGROK_DATA_ROOT, "body_include") 68941332aaSVladimir KotalOPENGROK_CONFIG_DIR = os.path.join(OPENGROK_BASE_DIR, "etc") 69941332aaSVladimir KotalOPENGROK_CONFIG_FILE = os.path.join(OPENGROK_CONFIG_DIR, 70b2d29daeSVladimir Kotal "configuration.xml") 71b2d29daeSVladimir KotalOPENGROK_WEBAPPS_DIR = os.path.join(tomcat_root, "webapps") 724810eb96SVladimir KotalOPENGROK_JAR = os.path.join(OPENGROK_LIB_DIR, 'opengrok.jar') 73b2d29daeSVladimir Kotal 745036d900SVladimir KotalNOMIRROR_ENV_NAME = 'NOMIRROR' 755036d900SVladimir Kotal 76f1835fddSVladimir Kotalexpected_token = None 77f1835fddSVladimir Kotal 78f1835fddSVladimir Kotalsleep_event = threading.Event() 79f1835fddSVladimir Kotalapp = Flask(__name__) 80f1835fddSVladimir Kotalauth = HTTPTokenAuth(scheme='Bearer') 814990cf90SVladimir KotalREINDEX_POINT = '/reindex' 82f1835fddSVladimir Kotal 83f1835fddSVladimir Kotal 84d4cbb62dSVladimir Kotaldef trigger_reindex(): 85d4cbb62dSVladimir Kotal # Signal the sync/indexer thread. 86d4cbb62dSVladimir Kotal sleep_event.set() 87d4cbb62dSVladimir Kotal sleep_event.clear() 88d4cbb62dSVladimir Kotal 89d4cbb62dSVladimir Kotal 90f1835fddSVladimir Kotal@auth.verify_token 91f1835fddSVladimir Kotaldef verify_token(token): 92f1835fddSVladimir Kotal if expected_token is None: 93f1835fddSVladimir Kotal return "yes" 94f1835fddSVladimir Kotal 95f1835fddSVladimir Kotal if token is not None and token == expected_token: 96f1835fddSVladimir Kotal return "yes" 97f1835fddSVladimir Kotal 98f1835fddSVladimir Kotal 994990cf90SVladimir Kotal@app.route(REINDEX_POINT) 100f1835fddSVladimir Kotal@auth.login_required 101f1835fddSVladimir Kotaldef index(): 102d4cbb62dSVladimir Kotal trigger_reindex() 103f1835fddSVladimir Kotal 104f1835fddSVladimir Kotal return "Reindex triggered" 105f1835fddSVladimir Kotal 106f1835fddSVladimir Kotal 107f1835fddSVladimir Kotaldef rest_function(logger, rest_port): 108f1835fddSVladimir Kotal logger.info("Starting REST app on port {}".format(rest_port)) 109f1835fddSVladimir Kotal serve(app, host="0.0.0.0", port=rest_port) 110f1835fddSVladimir Kotal 111b2d29daeSVladimir Kotal 112f7d6d77cSVladimir Kotaldef set_url_root(logger, url_root): 113b2d29daeSVladimir Kotal """ 114b2d29daeSVladimir Kotal Set URL root and URI based on input 115788f14d3SVladimir Kotal :param logger: logger instance 116b2d29daeSVladimir Kotal :param url_root: input 117b2d29daeSVladimir Kotal :return: URI and URL root 118b2d29daeSVladimir Kotal """ 119b2d29daeSVladimir Kotal if not url_root: 120b2d29daeSVladimir Kotal url_root = '/' 121b2d29daeSVladimir Kotal 122b2d29daeSVladimir Kotal if ' ' in url_root: 123b2d29daeSVladimir Kotal logger.warn('Deployment path contains spaces. Deploying to root') 124b2d29daeSVladimir Kotal url_root = '/' 125b2d29daeSVladimir Kotal 126b2d29daeSVladimir Kotal # Remove leading and trailing slashes 127b2d29daeSVladimir Kotal if url_root.startswith('/'): 128b2d29daeSVladimir Kotal url_root = url_root[1:] 129b2d29daeSVladimir Kotal if url_root.endswith('/'): 130b2d29daeSVladimir Kotal url_root = url_root[:-1] 131b2d29daeSVladimir Kotal 132b2d29daeSVladimir Kotal uri = "http://localhost:8080/" + url_root 133b2d29daeSVladimir Kotal # 134b2d29daeSVladimir Kotal # Make sure URI ends with slash. This is important for the various API 135b2d29daeSVladimir Kotal # calls, notably for those that check the HTTP error code. 136b2d29daeSVladimir Kotal # Normally accessing the URI without the terminating slash results in 137b2d29daeSVladimir Kotal # HTTP redirect (code 302) instead of success (200). 138b2d29daeSVladimir Kotal # 139b2d29daeSVladimir Kotal if not uri.endswith('/'): 140b2d29daeSVladimir Kotal uri = uri + '/' 141b2d29daeSVladimir Kotal 142b2d29daeSVladimir Kotal return uri, url_root 143b2d29daeSVladimir Kotal 144b2d29daeSVladimir Kotal 145b2d29daeSVladimir Kotaldef get_war_name(url_root): 146d44cbea0SVladimir Kotal """ 147d44cbea0SVladimir Kotal :param url_root: web app URL root 148d44cbea0SVladimir Kotal :return: filename of the WAR file 149d44cbea0SVladimir Kotal """ 150b2d29daeSVladimir Kotal if len(url_root) == 0: 151b2d29daeSVladimir Kotal return "ROOT.war" 152f7d6d77cSVladimir Kotal 153b2d29daeSVladimir Kotal return url_root + ".war" 154b2d29daeSVladimir Kotal 155b2d29daeSVladimir Kotal 156f7d6d77cSVladimir Kotaldef deploy(logger, url_root): 157b2d29daeSVladimir Kotal """ 158b2d29daeSVladimir Kotal Deploy the web application 159788f14d3SVladimir Kotal :param logger: logger instance 160b2d29daeSVladimir Kotal :param url_root: web app URL root 161b2d29daeSVladimir Kotal """ 162b2d29daeSVladimir Kotal 163b2d29daeSVladimir Kotal logger.info('Deploying web application') 164b2d29daeSVladimir Kotal webapps_dir = os.path.join(tomcat_root, 'webapps') 165b2d29daeSVladimir Kotal if not os.path.isdir(webapps_dir): 166b2d29daeSVladimir Kotal raise Exception("{} is not a directory".format(webapps_dir)) 167b2d29daeSVladimir Kotal 168b2d29daeSVladimir Kotal for item in os.listdir(webapps_dir): 169b2d29daeSVladimir Kotal subdir = os.path.join(webapps_dir, item) 170b2d29daeSVladimir Kotal if os.path.isdir(subdir): 171b2d29daeSVladimir Kotal logger.debug("Removing '{}' directory recursively".format(subdir)) 172b2d29daeSVladimir Kotal shutil.rmtree(subdir) 173b2d29daeSVladimir Kotal 174b2d29daeSVladimir Kotal deploy_war(logger, os.path.join(OPENGROK_LIB_DIR, "source.war"), 175b2d29daeSVladimir Kotal os.path.join(OPENGROK_WEBAPPS_DIR, get_war_name(url_root)), 176b2d29daeSVladimir Kotal OPENGROK_CONFIG_FILE, None) 177b2d29daeSVladimir Kotal 178b2d29daeSVladimir Kotal 179f7d6d77cSVladimir Kotaldef setup_redirect_source(logger, url_root): 180b2d29daeSVladimir Kotal """ 181b2d29daeSVladimir Kotal Set up redirect from /source 182b2d29daeSVladimir Kotal """ 183b2d29daeSVladimir Kotal logger.debug("Setting up redirect from /source to '{}'".format(url_root)) 184b2d29daeSVladimir Kotal source_dir = os.path.join(OPENGROK_WEBAPPS_DIR, "source") 185b2d29daeSVladimir Kotal if not os.path.isdir(source_dir): 186b2d29daeSVladimir Kotal os.makedirs(source_dir) 187b2d29daeSVladimir Kotal 188b2d29daeSVladimir Kotal with open(os.path.join(source_dir, "index.jsp"), "w+") as index: 189b2d29daeSVladimir Kotal index.write("<% response.sendRedirect(\"/{}\"); %>".format(url_root)) 190b2d29daeSVladimir Kotal 191b2d29daeSVladimir Kotal 192f7d6d77cSVladimir Kotaldef wait_for_tomcat(logger, uri): 193b2d29daeSVladimir Kotal """ 194b2d29daeSVladimir Kotal Active/busy waiting for Tomcat to come up. 195b2d29daeSVladimir Kotal Currently there is no upper time bound. 196b2d29daeSVladimir Kotal """ 197b2d29daeSVladimir Kotal logger.info("Waiting for Tomcat to start") 198b2d29daeSVladimir Kotal 199b2d29daeSVladimir Kotal while True: 200b2d29daeSVladimir Kotal try: 201d44cbea0SVladimir Kotal ret = get(uri) 202d44cbea0SVladimir Kotal status = ret.status_code 203b2d29daeSVladimir Kotal except ConnectionError: 204b2d29daeSVladimir Kotal status = 0 205b2d29daeSVladimir Kotal 206b2d29daeSVladimir Kotal if status != 200: 207b2d29daeSVladimir Kotal logger.debug("Got status {} for {}, sleeping for 1 second". 208b2d29daeSVladimir Kotal format(status, uri)) 209b2d29daeSVladimir Kotal time.sleep(1) 210b2d29daeSVladimir Kotal else: 211b2d29daeSVladimir Kotal break 212b2d29daeSVladimir Kotal 213b2d29daeSVladimir Kotal logger.info("Tomcat is ready") 214b2d29daeSVladimir Kotal 215b2d29daeSVladimir Kotal 216f7d6d77cSVladimir Kotaldef refresh_projects(logger, uri): 217b2d29daeSVladimir Kotal """ 218b2d29daeSVladimir Kotal Ensure each immediate source root subdirectory is a project. 219b2d29daeSVladimir Kotal """ 220b2d29daeSVladimir Kotal webapp_projects = list_projects(logger, uri) 2216100daaaSVladimir Kotal if not webapp_projects: 2226100daaaSVladimir Kotal return 2236100daaaSVladimir Kotal 224b2d29daeSVladimir Kotal logger.debug('Projects from the web app: {}'.format(webapp_projects)) 225b2d29daeSVladimir Kotal src_root = OPENGROK_SRC_ROOT 226b2d29daeSVladimir Kotal 227b2d29daeSVladimir Kotal # Add projects. 228b2d29daeSVladimir Kotal for item in os.listdir(src_root): 229b2d29daeSVladimir Kotal logger.debug('Got item {}'.format(item)) 230b2d29daeSVladimir Kotal if os.path.isdir(os.path.join(src_root, item)): 231b2d29daeSVladimir Kotal if item not in webapp_projects: 232b2d29daeSVladimir Kotal logger.info("Adding project {}".format(item)) 233b2d29daeSVladimir Kotal add_project(logger, item, uri) 234b2d29daeSVladimir Kotal 235b2d29daeSVladimir Kotal # Remove projects 236b2d29daeSVladimir Kotal for item in webapp_projects: 237b2d29daeSVladimir Kotal if not os.path.isdir(os.path.join(src_root, item)): 238b2d29daeSVladimir Kotal logger.info("Deleting project {}".format(item)) 239b2d29daeSVladimir Kotal delete_project(logger, item, uri) 240b2d29daeSVladimir Kotal 241b2d29daeSVladimir Kotal 242f7d6d77cSVladimir Kotaldef save_config(logger, uri, config_path): 243b2d29daeSVladimir Kotal """ 244b2d29daeSVladimir Kotal Retrieve configuration from the web app and write it to file. 245788f14d3SVladimir Kotal :param logger: logger instance 246b2d29daeSVladimir Kotal :param uri: web app URI 247b2d29daeSVladimir Kotal :param config_path: file path 248b2d29daeSVladimir Kotal """ 249b2d29daeSVladimir Kotal 250b2d29daeSVladimir Kotal config = get_configuration(logger, uri) 251c3904b2eSVladimir Kotal if config is None: 252c3904b2eSVladimir Kotal return 253c3904b2eSVladimir Kotal 254c3904b2eSVladimir Kotal logger.info('Saving configuration to {}'.format(config_path)) 255b2d29daeSVladimir Kotal with open(config_path, "w+") as config_file: 256b2d29daeSVladimir Kotal config_file.write(config) 257b2d29daeSVladimir Kotal 258b2d29daeSVladimir Kotal 259b2d29daeSVladimir Kotaldef merge_commands_env(commands, env): 260b2d29daeSVladimir Kotal """ 261b2d29daeSVladimir Kotal Merge environment into command structure. If any of the commands has 262b2d29daeSVladimir Kotal an environment already set, the env is merged in. 263b2d29daeSVladimir Kotal :param commands: commands structure 264b2d29daeSVladimir Kotal :param env: environment dictionary 265b2d29daeSVladimir Kotal :return: updated commands structure 266b2d29daeSVladimir Kotal """ 267b2d29daeSVladimir Kotal for cmd in commands: 268b2d29daeSVladimir Kotal cmd_env = cmd.get('env') 269b2d29daeSVladimir Kotal if cmd_env: 270b2d29daeSVladimir Kotal cmd.env.update(env) 271b2d29daeSVladimir Kotal else: 272b2d29daeSVladimir Kotal cmd['env'] = env 273b2d29daeSVladimir Kotal 274b2d29daeSVladimir Kotal return commands 275b2d29daeSVladimir Kotal 276b2d29daeSVladimir Kotal 277d4cbb62dSVladimir Kotaldef indexer_no_projects(logger, uri, config_path, extra_indexer_options): 2784810eb96SVladimir Kotal """ 2794810eb96SVladimir Kotal Project less indexer 2804810eb96SVladimir Kotal """ 2814810eb96SVladimir Kotal 2824810eb96SVladimir Kotal wait_for_tomcat(logger, uri) 2834810eb96SVladimir Kotal 2844810eb96SVladimir Kotal while True: 2854810eb96SVladimir Kotal indexer_options = ['-s', OPENGROK_SRC_ROOT, 2864810eb96SVladimir Kotal '-d', OPENGROK_DATA_ROOT, 2874810eb96SVladimir Kotal '-c', '/usr/local/bin/ctags', 2884810eb96SVladimir Kotal '--remote', 'on', 2894810eb96SVladimir Kotal '-H', 2904810eb96SVladimir Kotal '-W', config_path, 2914810eb96SVladimir Kotal '-U', uri] 2924810eb96SVladimir Kotal if extra_indexer_options: 2934810eb96SVladimir Kotal logger.debug("Adding extra indexer options: {}". 2944810eb96SVladimir Kotal format(extra_indexer_options)) 2954810eb96SVladimir Kotal indexer_options.extend(extra_indexer_options.split()) 2964810eb96SVladimir Kotal indexer = Indexer(indexer_options, logger=logger, 297d2f093daSVladimir Kotal jar=OPENGROK_JAR, doprint=True) 2984810eb96SVladimir Kotal indexer.execute() 2994810eb96SVladimir Kotal 300d4cbb62dSVladimir Kotal logger.info("Waiting for reindex to be triggered") 301b531e965SVladimir Kotal sleep_event.wait() 3024810eb96SVladimir Kotal 3034810eb96SVladimir Kotal 304d4cbb62dSVladimir Kotaldef timeout_loop(logger, sync_period): 305d4cbb62dSVladimir Kotal while True: 306d4cbb62dSVladimir Kotal sleep_seconds = sync_period * 60 307d4cbb62dSVladimir Kotal logger.info("Sleeping for {} seconds".format(sleep_seconds)) 308d4cbb62dSVladimir Kotal time.sleep(sleep_seconds) 309d4cbb62dSVladimir Kotal 310d4cbb62dSVladimir Kotal trigger_reindex() 311d4cbb62dSVladimir Kotal 312d4cbb62dSVladimir Kotal 313d4cbb62dSVladimir Kotaldef project_syncer(logger, loglevel, uri, config_path, numworkers, env): 314b2d29daeSVladimir Kotal """ 315b2d29daeSVladimir Kotal Wrapper for running opengrok-sync. 316b2d29daeSVladimir Kotal To be run in a thread/process in the background. 317b2d29daeSVladimir Kotal """ 318b2d29daeSVladimir Kotal 319f7d6d77cSVladimir Kotal wait_for_tomcat(logger, uri) 320b2d29daeSVladimir Kotal 321b2d29daeSVladimir Kotal while True: 322f7d6d77cSVladimir Kotal refresh_projects(logger, uri) 323b2d29daeSVladimir Kotal 324b2d29daeSVladimir Kotal if os.environ.get('OPENGROK_SYNC_YML'): # debug only 325b2d29daeSVladimir Kotal config_file = os.environ.get('OPENGROK_SYNC_YML') 326b2d29daeSVladimir Kotal else: 327b2d29daeSVladimir Kotal config_file = os.path.join(fs_root, 'scripts', 'sync.yml') 328b2d29daeSVladimir Kotal config = read_config(logger, config_file) 329b2d29daeSVladimir Kotal if config is None: 330b2d29daeSVladimir Kotal logger.error("Cannot read config file from {}".format(config_file)) 331b2d29daeSVladimir Kotal raise Exception("no sync config") 332b2d29daeSVladimir Kotal 333b2d29daeSVladimir Kotal projects = list_projects(logger, uri) 3346100daaaSVladimir Kotal if projects: 335b2d29daeSVladimir Kotal # 336b2d29daeSVladimir Kotal # The driveon=True is needed for the initial indexing of newly 3376100daaaSVladimir Kotal # added project, otherwise the incoming check in the 3386100daaaSVladimir Kotal # opengrok-mirror program would short circuit it. 339b2d29daeSVladimir Kotal # 340b2d29daeSVladimir Kotal if env: 341b2d29daeSVladimir Kotal logger.info('Merging commands with environment') 342b2d29daeSVladimir Kotal commands = merge_commands_env(config["commands"], env) 343b2d29daeSVladimir Kotal logger.debug(config['commands']) 344b2d29daeSVladimir Kotal else: 345b2d29daeSVladimir Kotal commands = config["commands"] 346b2d29daeSVladimir Kotal 347b2d29daeSVladimir Kotal logger.info("Sync starting") 348b2d29daeSVladimir Kotal do_sync(loglevel, commands, config.get('cleanup'), 349b2d29daeSVladimir Kotal projects, config.get("ignore_errors"), uri, 350b2d29daeSVladimir Kotal numworkers, driveon=True, logger=logger, print_output=True) 351b2d29daeSVladimir Kotal logger.info("Sync done") 352b2d29daeSVladimir Kotal 353b2d29daeSVladimir Kotal # Workaround for https://github.com/oracle/opengrok/issues/1670 354b2d29daeSVladimir Kotal Path(os.path.join(OPENGROK_DATA_ROOT, 'timestamp')).touch() 355b2d29daeSVladimir Kotal 356f7d6d77cSVladimir Kotal save_config(logger, uri, config_path) 357b2d29daeSVladimir Kotal 358d4cbb62dSVladimir Kotal logger.info("Waiting for reindex to be triggered") 359b531e965SVladimir Kotal sleep_event.wait() 360b2d29daeSVladimir Kotal 361b2d29daeSVladimir Kotal 36219f6e235SVladimir Kotaldef create_bare_config(logger, use_projects, extra_indexer_options=None): 363b2d29daeSVladimir Kotal """ 364b2d29daeSVladimir Kotal Create bare configuration file with a few basic settings. 365b2d29daeSVladimir Kotal """ 366b2d29daeSVladimir Kotal 367b2d29daeSVladimir Kotal logger.info('Creating bare configuration in {}'. 368b2d29daeSVladimir Kotal format(OPENGROK_CONFIG_FILE)) 3694810eb96SVladimir Kotal indexer_options = ['-s', OPENGROK_SRC_ROOT, 370b2d29daeSVladimir Kotal '-d', OPENGROK_DATA_ROOT, 371b2d29daeSVladimir Kotal '-c', '/usr/local/bin/ctags', 372b2d29daeSVladimir Kotal '--remote', 'on', 3734810eb96SVladimir Kotal '-H', 37419f6e235SVladimir Kotal '-S', 375b2d29daeSVladimir Kotal '-W', OPENGROK_CONFIG_FILE, 3764810eb96SVladimir Kotal '--noIndex'] 3774810eb96SVladimir Kotal 3789d6d7c28SVladimir Kotal if extra_indexer_options: 3795b279385SVladimir Kotal if type(extra_indexer_options) is not list: 3805b279385SVladimir Kotal raise Exception("extra_indexer_options has to be a list") 3819d6d7c28SVladimir Kotal indexer_options.extend(extra_indexer_options) 38219f6e235SVladimir Kotal if use_projects: 38319f6e235SVladimir Kotal indexer_options.append('-P') 3844810eb96SVladimir Kotal indexer = Indexer(indexer_options, 3854810eb96SVladimir Kotal jar=OPENGROK_JAR, 386b2d29daeSVladimir Kotal logger=logger, doprint=True) 387b2d29daeSVladimir Kotal indexer.execute() 388b2d29daeSVladimir Kotal ret = indexer.getretcode() 389b2d29daeSVladimir Kotal if ret != SUCCESS_EXITVAL: 390b2d29daeSVladimir Kotal logger.error('Command returned {}'.format(ret)) 391b2d29daeSVladimir Kotal logger.error(indexer.geterroutput()) 392b2d29daeSVladimir Kotal raise Exception("Failed to create bare configuration") 393b2d29daeSVladimir Kotal 394b2d29daeSVladimir Kotal 395f1835fddSVladimir Kotaldef get_num_from_env(logger, env_name, default_value): 396f1835fddSVladimir Kotal value = default_value 397f1835fddSVladimir Kotal env_str = os.environ.get(env_name) 398f1835fddSVladimir Kotal if env_str: 399f1835fddSVladimir Kotal try: 400f1835fddSVladimir Kotal n = int(env_str) 401f1835fddSVladimir Kotal if n >= 0: 402f1835fddSVladimir Kotal value = n 403f1835fddSVladimir Kotal except ValueError: 404f1835fddSVladimir Kotal logger.error("{} is not a number: {}". 405f1835fddSVladimir Kotal format(env_name, env_str)) 406f1835fddSVladimir Kotal 407f1835fddSVladimir Kotal return value 408f1835fddSVladimir Kotal 409f1835fddSVladimir Kotal 410f750ae73SVladimir Kotaldef check_index_and_wipe_out(logger): 411f750ae73SVladimir Kotal """ 412f750ae73SVladimir Kotal Check index by running the indexer. If the index does not match 413f750ae73SVladimir Kotal currently running version and the CHECK_INDEX environment variable 414f750ae73SVladimir Kotal is non empty, wipe out the directories under data root. 415f750ae73SVladimir Kotal """ 416f750ae73SVladimir Kotal check_index = os.environ.get('CHECK_INDEX') 417f750ae73SVladimir Kotal if check_index and os.path.exists(OPENGROK_CONFIG_FILE): 418f750ae73SVladimir Kotal logger.info('Checking if index matches current version') 419f750ae73SVladimir Kotal indexer_options = ['-R', OPENGROK_CONFIG_FILE, '--checkIndex'] 420f750ae73SVladimir Kotal indexer = Indexer(indexer_options, logger=logger, 421f750ae73SVladimir Kotal jar=OPENGROK_JAR, doprint=True) 422f750ae73SVladimir Kotal indexer.execute() 423f750ae73SVladimir Kotal if indexer.getretcode() == 1: 424f750ae73SVladimir Kotal logger.info('Wiping out data root') 425f750ae73SVladimir Kotal root = OPENGROK_DATA_ROOT 426f750ae73SVladimir Kotal for entry in os.listdir(root): 427f750ae73SVladimir Kotal path = os.path.join(root, entry) 428f750ae73SVladimir Kotal if os.path.isdir(path): 429f750ae73SVladimir Kotal try: 430f750ae73SVladimir Kotal logger.info("Removing '{}'".format(path)) 431f750ae73SVladimir Kotal shutil.rmtree(path) 432f750ae73SVladimir Kotal except Exception as e: 433f750ae73SVladimir Kotal logger.error("cannot delete '{}': {}".format(path, e)) 434f750ae73SVladimir Kotal 435f750ae73SVladimir Kotal 436d4cbb62dSVladimir Kotaldef start_rest_thread(logger): 437d4cbb62dSVladimir Kotal rest_port = get_num_from_env(logger, 'REST_PORT', 5000) 438d4cbb62dSVladimir Kotal token = os.environ.get('REST_TOKEN') 439d4cbb62dSVladimir Kotal global expected_token 440d4cbb62dSVladimir Kotal if token: 441d4cbb62dSVladimir Kotal logger.debug("Setting expected token for REST endpoint" 442d4cbb62dSVladimir Kotal "on port {}".format(rest_port)) 443d4cbb62dSVladimir Kotal expected_token = token 444d4cbb62dSVladimir Kotal logger.debug("Starting REST thread to listen for requests " 445d4cbb62dSVladimir Kotal "on port {} on the {} endpoint". 446d4cbb62dSVladimir Kotal format(rest_port, REINDEX_POINT)) 447d4cbb62dSVladimir Kotal rest_thread = threading.Thread(target=rest_function, 448d4cbb62dSVladimir Kotal name="REST thread", 449d4cbb62dSVladimir Kotal args=(logger, rest_port), daemon=True) 450d4cbb62dSVladimir Kotal rest_thread.start() 451d4cbb62dSVladimir Kotal 452d4cbb62dSVladimir Kotal 453d4cbb62dSVladimir Kotaldef start_timeout_thread(logger, sync_period): 45486d11813SVladimir Kotal logger.debug("Starting timeout thread") 455d4cbb62dSVladimir Kotal thread = threading.Thread(target=timeout_loop, 456d4cbb62dSVladimir Kotal name="Timeout thread", 457d4cbb62dSVladimir Kotal args=(logger, sync_period), daemon=True) 458d4cbb62dSVladimir Kotal thread.start() 459d4cbb62dSVladimir Kotal 460d4cbb62dSVladimir Kotal 461f7d6d77cSVladimir Kotaldef main(): 462b2d29daeSVladimir Kotal log_level = os.environ.get('OPENGROK_LOG_LEVEL') 463b2d29daeSVladimir Kotal if log_level: 464b2d29daeSVladimir Kotal log_level = get_log_level(log_level) 465b2d29daeSVladimir Kotal else: 466b2d29daeSVladimir Kotal log_level = logging.INFO 467b2d29daeSVladimir Kotal 468b2d29daeSVladimir Kotal logger = get_console_logger(get_class_basename(), log_level) 469b2d29daeSVladimir Kotal 470*85ee3da3SVladimir Kotal try: 471*85ee3da3SVladimir Kotal with open(os.path.join(OPENGROK_BASE_DIR, "VERSION"), "r") as f: 472*85ee3da3SVladimir Kotal version = f.read() 473*85ee3da3SVladimir Kotal logger.info("Running version {}".format(version)) 474*85ee3da3SVladimir Kotal except Exception: 475*85ee3da3SVladimir Kotal pass 476*85ee3da3SVladimir Kotal 477f7d6d77cSVladimir Kotal uri, url_root = set_url_root(logger, os.environ.get('URL_ROOT')) 478f7d6d77cSVladimir Kotal logger.debug("URL_ROOT = {}".format(url_root)) 479f7d6d77cSVladimir Kotal logger.debug("URI = {}".format(uri)) 480b2d29daeSVladimir Kotal 48154289af4SVladimir Kotal sync_period = get_num_from_env(logger, 'SYNC_PERIOD_MINUTES', 10) 4823d5852a4SVladimir Kotal if sync_period == 0: 483b531e965SVladimir Kotal logger.info("periodic synchronization disabled") 484b2d29daeSVladimir Kotal else: 4853d5852a4SVladimir Kotal logger.info("synchronization period = {} minutes".format(sync_period)) 486b2d29daeSVladimir Kotal 487b2d29daeSVladimir Kotal # Note that deploy is done before Tomcat is started. 488f7d6d77cSVladimir Kotal deploy(logger, url_root) 489b2d29daeSVladimir Kotal 490f7d6d77cSVladimir Kotal if url_root != '/source': 491f7d6d77cSVladimir Kotal setup_redirect_source(logger, url_root) 492b2d29daeSVladimir Kotal 493b2d29daeSVladimir Kotal env = {} 494dadacd3dSAdam Hornacek extra_indexer_options = os.environ.get('INDEXER_OPT', '') 4954810eb96SVladimir Kotal if extra_indexer_options: 4964810eb96SVladimir Kotal logger.info("extra indexer options: {}".format(extra_indexer_options)) 4974810eb96SVladimir Kotal env['OPENGROK_INDEXER_OPTIONAL_ARGS'] = extra_indexer_options 4985036d900SVladimir Kotal if os.environ.get(NOMIRROR_ENV_NAME): 4995036d900SVladimir Kotal env['OPENGROK_NO_MIRROR'] = os.environ.get(NOMIRROR_ENV_NAME) 500b2d29daeSVladimir Kotal logger.debug('Extra environment: {}'.format(env)) 501b2d29daeSVladimir Kotal 5024810eb96SVladimir Kotal use_projects = True 5034810eb96SVladimir Kotal if os.environ.get('AVOID_PROJECTS'): 5044810eb96SVladimir Kotal use_projects = False 5054810eb96SVladimir Kotal 506b2d29daeSVladimir Kotal # 507b2d29daeSVladimir Kotal # Create empty configuration to avoid the non existent file exception 508b2d29daeSVladimir Kotal # in the web app during the first web app startup. 509b2d29daeSVladimir Kotal # 510b2d29daeSVladimir Kotal if not os.path.exists(OPENGROK_CONFIG_FILE) or \ 511b2d29daeSVladimir Kotal os.path.getsize(OPENGROK_CONFIG_FILE) == 0: 51219f6e235SVladimir Kotal create_bare_config(logger, use_projects, extra_indexer_options.split()) 513b2d29daeSVladimir Kotal 514b9361335SVladimir Kotal # 515f750ae73SVladimir Kotal # Index check needs read-only configuration so it is placed 516f750ae73SVladimir Kotal # right after create_bare_config(). 517f750ae73SVladimir Kotal # 518f750ae73SVladimir Kotal check_index_and_wipe_out(logger) 519f750ae73SVladimir Kotal 520f750ae73SVladimir Kotal # 521b9361335SVladimir Kotal # If there is read-only configuration file, merge it with current 522b9361335SVladimir Kotal # configuration. 523b9361335SVladimir Kotal # 524b9361335SVladimir Kotal read_only_config_file = os.environ.get('READONLY_CONFIG_FILE') 525b9361335SVladimir Kotal if read_only_config_file and os.path.exists(read_only_config_file): 526b9361335SVladimir Kotal logger.info('Merging read-only configuration from \'{}\' with current ' 527b9361335SVladimir Kotal 'configuration in \'{}\''.format(read_only_config_file, 528b9361335SVladimir Kotal OPENGROK_CONFIG_FILE)) 529b9361335SVladimir Kotal out_file = None 530b9361335SVladimir Kotal with tempfile.NamedTemporaryFile(mode='w+', delete=False, 531b9361335SVladimir Kotal prefix='merged_config') as tmp_out: 53270e4aafbSVladimir Kotal out_file = tmp_out.name 533b9361335SVladimir Kotal merge_config_files(read_only_config_file, OPENGROK_CONFIG_FILE, 534b9361335SVladimir Kotal tmp_out, jar=OPENGROK_JAR, loglevel=log_level) 535b9361335SVladimir Kotal 536b9361335SVladimir Kotal if out_file and os.path.getsize(out_file) > 0: 537b9361335SVladimir Kotal shutil.move(tmp_out.name, OPENGROK_CONFIG_FILE) 538b9361335SVladimir Kotal else: 539b9361335SVladimir Kotal logger.warning('Failed to merge read-only configuration, ' 540b9361335SVladimir Kotal 'leaving the original in place') 54170e4aafbSVladimir Kotal if out_file: 54270e4aafbSVladimir Kotal os.remove(out_file) 543b9361335SVladimir Kotal 5445036d900SVladimir Kotal sync_enabled = True 5454810eb96SVladimir Kotal if use_projects: 546f1835fddSVladimir Kotal num_workers = get_num_from_env(logger, 'WORKERS', 547f1835fddSVladimir Kotal multiprocessing.cpu_count()) 548b2d29daeSVladimir Kotal logger.info('Number of sync workers: {}'.format(num_workers)) 549b2d29daeSVladimir Kotal 5505036d900SVladimir Kotal if not os.environ.get(NOMIRROR_ENV_NAME): 551941332aaSVladimir Kotal mirror_config = os.path.join(OPENGROK_CONFIG_DIR, "mirror.yml") 552941332aaSVladimir Kotal if not os.path.exists(mirror_config): 553941332aaSVladimir Kotal with open(mirror_config, 'w') as fp: 554fa3facf6SVladimir Kotal fp.write("# Empty config file for opengrok-mirror\n") 5555036d900SVladimir Kotal else: 5565036d900SVladimir Kotal conf = read_config(logger, mirror_config) 5575036d900SVladimir Kotal logger.info("Checking mirror configuration in '{}'". 5585036d900SVladimir Kotal format(mirror_config)) 5595036d900SVladimir Kotal if not check_configuration(conf): 5605036d900SVladimir Kotal logger.error("Mirror configuration in '{}' is invalid, " 5615036d900SVladimir Kotal "disabling sync".format(mirror_config)) 5625036d900SVladimir Kotal sync_enabled = False 563941332aaSVladimir Kotal 5644810eb96SVladimir Kotal worker_function = project_syncer 5654810eb96SVladimir Kotal syncer_args = (logger, log_level, uri, 566f7d6d77cSVladimir Kotal OPENGROK_CONFIG_FILE, 567d4cbb62dSVladimir Kotal num_workers, env) 5684810eb96SVladimir Kotal else: 5694810eb96SVladimir Kotal worker_function = indexer_no_projects 570d4cbb62dSVladimir Kotal syncer_args = (logger, uri, OPENGROK_CONFIG_FILE, 5714810eb96SVladimir Kotal extra_indexer_options) 5724810eb96SVladimir Kotal 5735036d900SVladimir Kotal if sync_enabled: 5744810eb96SVladimir Kotal logger.debug("Starting sync thread") 5755036d900SVladimir Kotal sync_thread = threading.Thread(target=worker_function, 5765036d900SVladimir Kotal name="Sync thread", 5776100daaaSVladimir Kotal args=syncer_args, daemon=True) 5786100daaaSVladimir Kotal sync_thread.start() 579b2d29daeSVladimir Kotal 580d4cbb62dSVladimir Kotal start_rest_thread(logger) 58186d11813SVladimir Kotal if sync_period > 0: 582d4cbb62dSVladimir Kotal start_timeout_thread(logger, sync_period) 583f1835fddSVladimir Kotal 584b2d29daeSVladimir Kotal # Start Tomcat last. It will be the foreground process. 585b2d29daeSVladimir Kotal logger.info("Starting Tomcat") 5866100daaaSVladimir Kotal global tomcat_popen 5876100daaaSVladimir Kotal tomcat_popen = subprocess.Popen([os.path.join(tomcat_root, 'bin', 5886100daaaSVladimir Kotal 'catalina.sh'), 5896100daaaSVladimir Kotal 'run']) 5906100daaaSVladimir Kotal tomcat_popen.wait() 591f7d6d77cSVladimir Kotal 592f7d6d77cSVladimir Kotal 593e6daae6cSVladimir Kotaldef signal_handler(signum, frame): 59423cd7cffSVladimir Kotal print("Received signal {}".format(signum)) 59523cd7cffSVladimir Kotal 5966100daaaSVladimir Kotal global tomcat_popen 5976100daaaSVladimir Kotal print("Terminating Tomcat {}".format(tomcat_popen)) 5986100daaaSVladimir Kotal tomcat_popen.terminate() 5996100daaaSVladimir Kotal 6009e27dd5fSVladimir Kotal sys.exit(0) 601e6daae6cSVladimir Kotal 602e6daae6cSVladimir Kotal 603e6daae6cSVladimir Kotalif __name__ == "__main__": 604e6daae6cSVladimir Kotal signal.signal(signal.SIGTERM, signal_handler) 605e6daae6cSVladimir Kotal signal.signal(signal.SIGINT, signal_handler) 606e6daae6cSVladimir Kotal 607e6daae6cSVladimir Kotal main() 608