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 50b2d29daeSVladimir Kotal 51b2d29daeSVladimir Kotal 52b2d29daeSVladimir Kotalfs_root = os.path.abspath('.').split(os.path.sep)[0] + os.path.sep 53b2d29daeSVladimir Kotalif os.environ.get('OPENGROK_TOMCAT_ROOT'): # debug only 54b2d29daeSVladimir Kotal tomcat_root = os.environ.get('OPENGROK_TOMCAT_ROOT') 55b2d29daeSVladimir Kotalelse: 56b2d29daeSVladimir Kotal tomcat_root = os.path.join(fs_root, "usr", "local", "tomcat") 57b2d29daeSVladimir Kotal 58b2d29daeSVladimir Kotalif os.environ.get('OPENGROK_ROOT'): # debug only 59b2d29daeSVladimir Kotal OPENGROK_BASE_DIR = os.environ.get('OPENGROK_ROOT') 60b2d29daeSVladimir Kotalelse: 61b2d29daeSVladimir Kotal OPENGROK_BASE_DIR = os.path.join(fs_root, "opengrok") 62b2d29daeSVladimir Kotal 63b2d29daeSVladimir KotalOPENGROK_LIB_DIR = os.path.join(OPENGROK_BASE_DIR, "lib") 64b2d29daeSVladimir KotalOPENGROK_DATA_ROOT = os.path.join(OPENGROK_BASE_DIR, "data") 65b2d29daeSVladimir KotalOPENGROK_SRC_ROOT = os.path.join(OPENGROK_BASE_DIR, "src") 66b2d29daeSVladimir KotalBODY_INCLUDE_FILE = os.path.join(OPENGROK_DATA_ROOT, "body_include") 67*941332aaSVladimir KotalOPENGROK_CONFIG_DIR = os.path.join(OPENGROK_BASE_DIR, "etc") 68*941332aaSVladimir KotalOPENGROK_CONFIG_FILE = os.path.join(OPENGROK_CONFIG_DIR, 69b2d29daeSVladimir Kotal "configuration.xml") 70b2d29daeSVladimir KotalOPENGROK_WEBAPPS_DIR = os.path.join(tomcat_root, "webapps") 714810eb96SVladimir KotalOPENGROK_JAR = os.path.join(OPENGROK_LIB_DIR, 'opengrok.jar') 72b2d29daeSVladimir Kotal 73f1835fddSVladimir Kotalexpected_token = None 74f1835fddSVladimir Kotal 75f1835fddSVladimir Kotalsleep_event = threading.Event() 76f1835fddSVladimir Kotalapp = Flask(__name__) 77f1835fddSVladimir Kotalauth = HTTPTokenAuth(scheme='Bearer') 784990cf90SVladimir KotalREINDEX_POINT = '/reindex' 79f1835fddSVladimir Kotal 80f1835fddSVladimir Kotal 81d4cbb62dSVladimir Kotaldef trigger_reindex(): 82d4cbb62dSVladimir Kotal # Signal the sync/indexer thread. 83d4cbb62dSVladimir Kotal sleep_event.set() 84d4cbb62dSVladimir Kotal sleep_event.clear() 85d4cbb62dSVladimir Kotal 86d4cbb62dSVladimir Kotal 87f1835fddSVladimir Kotal@auth.verify_token 88f1835fddSVladimir Kotaldef verify_token(token): 89f1835fddSVladimir Kotal if expected_token is None: 90f1835fddSVladimir Kotal return "yes" 91f1835fddSVladimir Kotal 92f1835fddSVladimir Kotal if token is not None and token == expected_token: 93f1835fddSVladimir Kotal return "yes" 94f1835fddSVladimir Kotal 95f1835fddSVladimir Kotal 964990cf90SVladimir Kotal@app.route(REINDEX_POINT) 97f1835fddSVladimir Kotal@auth.login_required 98f1835fddSVladimir Kotaldef index(): 99d4cbb62dSVladimir Kotal trigger_reindex() 100f1835fddSVladimir Kotal 101f1835fddSVladimir Kotal return "Reindex triggered" 102f1835fddSVladimir Kotal 103f1835fddSVladimir Kotal 104f1835fddSVladimir Kotaldef rest_function(logger, rest_port): 105f1835fddSVladimir Kotal logger.info("Starting REST app on port {}".format(rest_port)) 106f1835fddSVladimir Kotal serve(app, host="0.0.0.0", port=rest_port) 107f1835fddSVladimir Kotal 108b2d29daeSVladimir Kotal 109f7d6d77cSVladimir Kotaldef set_url_root(logger, url_root): 110b2d29daeSVladimir Kotal """ 111b2d29daeSVladimir Kotal Set URL root and URI based on input 112788f14d3SVladimir Kotal :param logger: logger instance 113b2d29daeSVladimir Kotal :param url_root: input 114b2d29daeSVladimir Kotal :return: URI and URL root 115b2d29daeSVladimir Kotal """ 116b2d29daeSVladimir Kotal if not url_root: 117b2d29daeSVladimir Kotal url_root = '/' 118b2d29daeSVladimir Kotal 119b2d29daeSVladimir Kotal if ' ' in url_root: 120b2d29daeSVladimir Kotal logger.warn('Deployment path contains spaces. Deploying to root') 121b2d29daeSVladimir Kotal url_root = '/' 122b2d29daeSVladimir Kotal 123b2d29daeSVladimir Kotal # Remove leading and trailing slashes 124b2d29daeSVladimir Kotal if url_root.startswith('/'): 125b2d29daeSVladimir Kotal url_root = url_root[1:] 126b2d29daeSVladimir Kotal if url_root.endswith('/'): 127b2d29daeSVladimir Kotal url_root = url_root[:-1] 128b2d29daeSVladimir Kotal 129b2d29daeSVladimir Kotal uri = "http://localhost:8080/" + url_root 130b2d29daeSVladimir Kotal # 131b2d29daeSVladimir Kotal # Make sure URI ends with slash. This is important for the various API 132b2d29daeSVladimir Kotal # calls, notably for those that check the HTTP error code. 133b2d29daeSVladimir Kotal # Normally accessing the URI without the terminating slash results in 134b2d29daeSVladimir Kotal # HTTP redirect (code 302) instead of success (200). 135b2d29daeSVladimir Kotal # 136b2d29daeSVladimir Kotal if not uri.endswith('/'): 137b2d29daeSVladimir Kotal uri = uri + '/' 138b2d29daeSVladimir Kotal 139b2d29daeSVladimir Kotal return uri, url_root 140b2d29daeSVladimir Kotal 141b2d29daeSVladimir Kotal 142b2d29daeSVladimir Kotaldef get_war_name(url_root): 143d44cbea0SVladimir Kotal """ 144d44cbea0SVladimir Kotal :param url_root: web app URL root 145d44cbea0SVladimir Kotal :return: filename of the WAR file 146d44cbea0SVladimir Kotal """ 147b2d29daeSVladimir Kotal if len(url_root) == 0: 148b2d29daeSVladimir Kotal return "ROOT.war" 149f7d6d77cSVladimir Kotal 150b2d29daeSVladimir Kotal return url_root + ".war" 151b2d29daeSVladimir Kotal 152b2d29daeSVladimir Kotal 153f7d6d77cSVladimir Kotaldef deploy(logger, url_root): 154b2d29daeSVladimir Kotal """ 155b2d29daeSVladimir Kotal Deploy the web application 156788f14d3SVladimir Kotal :param logger: logger instance 157b2d29daeSVladimir Kotal :param url_root: web app URL root 158b2d29daeSVladimir Kotal """ 159b2d29daeSVladimir Kotal 160b2d29daeSVladimir Kotal logger.info('Deploying web application') 161b2d29daeSVladimir Kotal webapps_dir = os.path.join(tomcat_root, 'webapps') 162b2d29daeSVladimir Kotal if not os.path.isdir(webapps_dir): 163b2d29daeSVladimir Kotal raise Exception("{} is not a directory".format(webapps_dir)) 164b2d29daeSVladimir Kotal 165b2d29daeSVladimir Kotal for item in os.listdir(webapps_dir): 166b2d29daeSVladimir Kotal subdir = os.path.join(webapps_dir, item) 167b2d29daeSVladimir Kotal if os.path.isdir(subdir): 168b2d29daeSVladimir Kotal logger.debug("Removing '{}' directory recursively".format(subdir)) 169b2d29daeSVladimir Kotal shutil.rmtree(subdir) 170b2d29daeSVladimir Kotal 171b2d29daeSVladimir Kotal deploy_war(logger, os.path.join(OPENGROK_LIB_DIR, "source.war"), 172b2d29daeSVladimir Kotal os.path.join(OPENGROK_WEBAPPS_DIR, get_war_name(url_root)), 173b2d29daeSVladimir Kotal OPENGROK_CONFIG_FILE, None) 174b2d29daeSVladimir Kotal 175b2d29daeSVladimir Kotal 176f7d6d77cSVladimir Kotaldef setup_redirect_source(logger, url_root): 177b2d29daeSVladimir Kotal """ 178b2d29daeSVladimir Kotal Set up redirect from /source 179b2d29daeSVladimir Kotal """ 180b2d29daeSVladimir Kotal logger.debug("Setting up redirect from /source to '{}'".format(url_root)) 181b2d29daeSVladimir Kotal source_dir = os.path.join(OPENGROK_WEBAPPS_DIR, "source") 182b2d29daeSVladimir Kotal if not os.path.isdir(source_dir): 183b2d29daeSVladimir Kotal os.makedirs(source_dir) 184b2d29daeSVladimir Kotal 185b2d29daeSVladimir Kotal with open(os.path.join(source_dir, "index.jsp"), "w+") as index: 186b2d29daeSVladimir Kotal index.write("<% response.sendRedirect(\"/{}\"); %>".format(url_root)) 187b2d29daeSVladimir Kotal 188b2d29daeSVladimir Kotal 189f7d6d77cSVladimir Kotaldef wait_for_tomcat(logger, uri): 190b2d29daeSVladimir Kotal """ 191b2d29daeSVladimir Kotal Active/busy waiting for Tomcat to come up. 192b2d29daeSVladimir Kotal Currently there is no upper time bound. 193b2d29daeSVladimir Kotal """ 194b2d29daeSVladimir Kotal logger.info("Waiting for Tomcat to start") 195b2d29daeSVladimir Kotal 196b2d29daeSVladimir Kotal while True: 197b2d29daeSVladimir Kotal try: 198d44cbea0SVladimir Kotal ret = get(uri) 199d44cbea0SVladimir Kotal status = ret.status_code 200b2d29daeSVladimir Kotal except ConnectionError: 201b2d29daeSVladimir Kotal status = 0 202b2d29daeSVladimir Kotal 203b2d29daeSVladimir Kotal if status != 200: 204b2d29daeSVladimir Kotal logger.debug("Got status {} for {}, sleeping for 1 second". 205b2d29daeSVladimir Kotal format(status, uri)) 206b2d29daeSVladimir Kotal time.sleep(1) 207b2d29daeSVladimir Kotal else: 208b2d29daeSVladimir Kotal break 209b2d29daeSVladimir Kotal 210b2d29daeSVladimir Kotal logger.info("Tomcat is ready") 211b2d29daeSVladimir Kotal 212b2d29daeSVladimir Kotal 213f7d6d77cSVladimir Kotaldef refresh_projects(logger, uri): 214b2d29daeSVladimir Kotal """ 215b2d29daeSVladimir Kotal Ensure each immediate source root subdirectory is a project. 216b2d29daeSVladimir Kotal """ 217b2d29daeSVladimir Kotal webapp_projects = list_projects(logger, uri) 2186100daaaSVladimir Kotal if not webapp_projects: 2196100daaaSVladimir Kotal return 2206100daaaSVladimir Kotal 221b2d29daeSVladimir Kotal logger.debug('Projects from the web app: {}'.format(webapp_projects)) 222b2d29daeSVladimir Kotal src_root = OPENGROK_SRC_ROOT 223b2d29daeSVladimir Kotal 224b2d29daeSVladimir Kotal # Add projects. 225b2d29daeSVladimir Kotal for item in os.listdir(src_root): 226b2d29daeSVladimir Kotal logger.debug('Got item {}'.format(item)) 227b2d29daeSVladimir Kotal if os.path.isdir(os.path.join(src_root, item)): 228b2d29daeSVladimir Kotal if item not in webapp_projects: 229b2d29daeSVladimir Kotal logger.info("Adding project {}".format(item)) 230b2d29daeSVladimir Kotal add_project(logger, item, uri) 231b2d29daeSVladimir Kotal 232b2d29daeSVladimir Kotal # Remove projects 233b2d29daeSVladimir Kotal for item in webapp_projects: 234b2d29daeSVladimir Kotal if not os.path.isdir(os.path.join(src_root, item)): 235b2d29daeSVladimir Kotal logger.info("Deleting project {}".format(item)) 236b2d29daeSVladimir Kotal delete_project(logger, item, uri) 237b2d29daeSVladimir Kotal 238b2d29daeSVladimir Kotal 239f7d6d77cSVladimir Kotaldef save_config(logger, uri, config_path): 240b2d29daeSVladimir Kotal """ 241b2d29daeSVladimir Kotal Retrieve configuration from the web app and write it to file. 242788f14d3SVladimir Kotal :param logger: logger instance 243b2d29daeSVladimir Kotal :param uri: web app URI 244b2d29daeSVladimir Kotal :param config_path: file path 245b2d29daeSVladimir Kotal """ 246b2d29daeSVladimir Kotal 247b2d29daeSVladimir Kotal logger.info('Saving configuration to {}'.format(config_path)) 248b2d29daeSVladimir Kotal config = get_configuration(logger, uri) 249b2d29daeSVladimir Kotal with open(config_path, "w+") as config_file: 250b2d29daeSVladimir Kotal config_file.write(config) 251b2d29daeSVladimir Kotal 252b2d29daeSVladimir Kotal 253b2d29daeSVladimir Kotaldef merge_commands_env(commands, env): 254b2d29daeSVladimir Kotal """ 255b2d29daeSVladimir Kotal Merge environment into command structure. If any of the commands has 256b2d29daeSVladimir Kotal an environment already set, the env is merged in. 257b2d29daeSVladimir Kotal :param commands: commands structure 258b2d29daeSVladimir Kotal :param env: environment dictionary 259b2d29daeSVladimir Kotal :return: updated commands structure 260b2d29daeSVladimir Kotal """ 261b2d29daeSVladimir Kotal for cmd in commands: 262b2d29daeSVladimir Kotal cmd_env = cmd.get('env') 263b2d29daeSVladimir Kotal if cmd_env: 264b2d29daeSVladimir Kotal cmd.env.update(env) 265b2d29daeSVladimir Kotal else: 266b2d29daeSVladimir Kotal cmd['env'] = env 267b2d29daeSVladimir Kotal 268b2d29daeSVladimir Kotal return commands 269b2d29daeSVladimir Kotal 270b2d29daeSVladimir Kotal 271d4cbb62dSVladimir Kotaldef indexer_no_projects(logger, uri, config_path, extra_indexer_options): 2724810eb96SVladimir Kotal """ 2734810eb96SVladimir Kotal Project less indexer 2744810eb96SVladimir Kotal """ 2754810eb96SVladimir Kotal 2764810eb96SVladimir Kotal wait_for_tomcat(logger, uri) 2774810eb96SVladimir Kotal 2784810eb96SVladimir Kotal while True: 2794810eb96SVladimir Kotal indexer_options = ['-s', OPENGROK_SRC_ROOT, 2804810eb96SVladimir Kotal '-d', OPENGROK_DATA_ROOT, 2814810eb96SVladimir Kotal '-c', '/usr/local/bin/ctags', 2824810eb96SVladimir Kotal '--remote', 'on', 2834810eb96SVladimir Kotal '-H', 2844810eb96SVladimir Kotal '-W', config_path, 2854810eb96SVladimir Kotal '-U', uri] 2864810eb96SVladimir Kotal if extra_indexer_options: 2874810eb96SVladimir Kotal logger.debug("Adding extra indexer options: {}". 2884810eb96SVladimir Kotal format(extra_indexer_options)) 2894810eb96SVladimir Kotal indexer_options.extend(extra_indexer_options.split()) 2904810eb96SVladimir Kotal indexer = Indexer(indexer_options, logger=logger, 291d2f093daSVladimir Kotal jar=OPENGROK_JAR, doprint=True) 2924810eb96SVladimir Kotal indexer.execute() 2934810eb96SVladimir Kotal 294d4cbb62dSVladimir Kotal logger.info("Waiting for reindex to be triggered") 295b531e965SVladimir Kotal sleep_event.wait() 2964810eb96SVladimir Kotal 2974810eb96SVladimir Kotal 298d4cbb62dSVladimir Kotaldef timeout_loop(logger, sync_period): 299d4cbb62dSVladimir Kotal while True: 300d4cbb62dSVladimir Kotal sleep_seconds = sync_period * 60 301d4cbb62dSVladimir Kotal logger.info("Sleeping for {} seconds".format(sleep_seconds)) 302d4cbb62dSVladimir Kotal time.sleep(sleep_seconds) 303d4cbb62dSVladimir Kotal 304d4cbb62dSVladimir Kotal trigger_reindex() 305d4cbb62dSVladimir Kotal 306d4cbb62dSVladimir Kotal 307d4cbb62dSVladimir Kotaldef project_syncer(logger, loglevel, uri, config_path, numworkers, env): 308b2d29daeSVladimir Kotal """ 309b2d29daeSVladimir Kotal Wrapper for running opengrok-sync. 310b2d29daeSVladimir Kotal To be run in a thread/process in the background. 311b2d29daeSVladimir Kotal """ 312b2d29daeSVladimir Kotal 313f7d6d77cSVladimir Kotal wait_for_tomcat(logger, uri) 314b2d29daeSVladimir Kotal 315b2d29daeSVladimir Kotal while True: 316f7d6d77cSVladimir Kotal refresh_projects(logger, uri) 317b2d29daeSVladimir Kotal 318b2d29daeSVladimir Kotal if os.environ.get('OPENGROK_SYNC_YML'): # debug only 319b2d29daeSVladimir Kotal config_file = os.environ.get('OPENGROK_SYNC_YML') 320b2d29daeSVladimir Kotal else: 321b2d29daeSVladimir Kotal config_file = os.path.join(fs_root, 'scripts', 'sync.yml') 322b2d29daeSVladimir Kotal config = read_config(logger, config_file) 323b2d29daeSVladimir Kotal if config is None: 324b2d29daeSVladimir Kotal logger.error("Cannot read config file from {}".format(config_file)) 325b2d29daeSVladimir Kotal raise Exception("no sync config") 326b2d29daeSVladimir Kotal 327b2d29daeSVladimir Kotal projects = list_projects(logger, uri) 3286100daaaSVladimir Kotal if projects: 329b2d29daeSVladimir Kotal # 330b2d29daeSVladimir Kotal # The driveon=True is needed for the initial indexing of newly 3316100daaaSVladimir Kotal # added project, otherwise the incoming check in the 3326100daaaSVladimir Kotal # opengrok-mirror program would short circuit it. 333b2d29daeSVladimir Kotal # 334b2d29daeSVladimir Kotal if env: 335b2d29daeSVladimir Kotal logger.info('Merging commands with environment') 336b2d29daeSVladimir Kotal commands = merge_commands_env(config["commands"], env) 337b2d29daeSVladimir Kotal logger.debug(config['commands']) 338b2d29daeSVladimir Kotal else: 339b2d29daeSVladimir Kotal commands = config["commands"] 340b2d29daeSVladimir Kotal 341b2d29daeSVladimir Kotal logger.info("Sync starting") 342b2d29daeSVladimir Kotal do_sync(loglevel, commands, config.get('cleanup'), 343b2d29daeSVladimir Kotal projects, config.get("ignore_errors"), uri, 344b2d29daeSVladimir Kotal numworkers, driveon=True, logger=logger, print_output=True) 345b2d29daeSVladimir Kotal logger.info("Sync done") 346b2d29daeSVladimir Kotal 347b2d29daeSVladimir Kotal # Workaround for https://github.com/oracle/opengrok/issues/1670 348b2d29daeSVladimir Kotal Path(os.path.join(OPENGROK_DATA_ROOT, 'timestamp')).touch() 349b2d29daeSVladimir Kotal 350f7d6d77cSVladimir Kotal save_config(logger, uri, config_path) 351b2d29daeSVladimir Kotal 352d4cbb62dSVladimir Kotal logger.info("Waiting for reindex to be triggered") 353b531e965SVladimir Kotal sleep_event.wait() 354b2d29daeSVladimir Kotal 355b2d29daeSVladimir Kotal 35619f6e235SVladimir Kotaldef create_bare_config(logger, use_projects, extra_indexer_options=None): 357b2d29daeSVladimir Kotal """ 358b2d29daeSVladimir Kotal Create bare configuration file with a few basic settings. 359b2d29daeSVladimir Kotal """ 360b2d29daeSVladimir Kotal 361b2d29daeSVladimir Kotal logger.info('Creating bare configuration in {}'. 362b2d29daeSVladimir Kotal format(OPENGROK_CONFIG_FILE)) 3634810eb96SVladimir Kotal indexer_options = ['-s', OPENGROK_SRC_ROOT, 364b2d29daeSVladimir Kotal '-d', OPENGROK_DATA_ROOT, 365b2d29daeSVladimir Kotal '-c', '/usr/local/bin/ctags', 366b2d29daeSVladimir Kotal '--remote', 'on', 3674810eb96SVladimir Kotal '-H', 36819f6e235SVladimir Kotal '-S', 369b2d29daeSVladimir Kotal '-W', OPENGROK_CONFIG_FILE, 3704810eb96SVladimir Kotal '--noIndex'] 3714810eb96SVladimir Kotal 3729d6d7c28SVladimir Kotal if extra_indexer_options: 3735b279385SVladimir Kotal if type(extra_indexer_options) is not list: 3745b279385SVladimir Kotal raise Exception("extra_indexer_options has to be a list") 3759d6d7c28SVladimir Kotal indexer_options.extend(extra_indexer_options) 37619f6e235SVladimir Kotal if use_projects: 37719f6e235SVladimir Kotal indexer_options.append('-P') 3784810eb96SVladimir Kotal indexer = Indexer(indexer_options, 3794810eb96SVladimir Kotal jar=OPENGROK_JAR, 380b2d29daeSVladimir Kotal logger=logger, doprint=True) 381b2d29daeSVladimir Kotal indexer.execute() 382b2d29daeSVladimir Kotal ret = indexer.getretcode() 383b2d29daeSVladimir Kotal if ret != SUCCESS_EXITVAL: 384b2d29daeSVladimir Kotal logger.error('Command returned {}'.format(ret)) 385b2d29daeSVladimir Kotal logger.error(indexer.geterroutput()) 386b2d29daeSVladimir Kotal raise Exception("Failed to create bare configuration") 387b2d29daeSVladimir Kotal 388b2d29daeSVladimir Kotal 389f1835fddSVladimir Kotaldef get_num_from_env(logger, env_name, default_value): 390f1835fddSVladimir Kotal value = default_value 391f1835fddSVladimir Kotal env_str = os.environ.get(env_name) 392f1835fddSVladimir Kotal if env_str: 393f1835fddSVladimir Kotal try: 394f1835fddSVladimir Kotal n = int(env_str) 395f1835fddSVladimir Kotal if n >= 0: 396f1835fddSVladimir Kotal value = n 397f1835fddSVladimir Kotal except ValueError: 398f1835fddSVladimir Kotal logger.error("{} is not a number: {}". 399f1835fddSVladimir Kotal format(env_name, env_str)) 400f1835fddSVladimir Kotal 401f1835fddSVladimir Kotal return value 402f1835fddSVladimir Kotal 403f1835fddSVladimir Kotal 404f750ae73SVladimir Kotaldef check_index_and_wipe_out(logger): 405f750ae73SVladimir Kotal """ 406f750ae73SVladimir Kotal Check index by running the indexer. If the index does not match 407f750ae73SVladimir Kotal currently running version and the CHECK_INDEX environment variable 408f750ae73SVladimir Kotal is non empty, wipe out the directories under data root. 409f750ae73SVladimir Kotal """ 410f750ae73SVladimir Kotal check_index = os.environ.get('CHECK_INDEX') 411f750ae73SVladimir Kotal if check_index and os.path.exists(OPENGROK_CONFIG_FILE): 412f750ae73SVladimir Kotal logger.info('Checking if index matches current version') 413f750ae73SVladimir Kotal indexer_options = ['-R', OPENGROK_CONFIG_FILE, '--checkIndex'] 414f750ae73SVladimir Kotal indexer = Indexer(indexer_options, logger=logger, 415f750ae73SVladimir Kotal jar=OPENGROK_JAR, doprint=True) 416f750ae73SVladimir Kotal indexer.execute() 417f750ae73SVladimir Kotal if indexer.getretcode() == 1: 418f750ae73SVladimir Kotal logger.info('Wiping out data root') 419f750ae73SVladimir Kotal root = OPENGROK_DATA_ROOT 420f750ae73SVladimir Kotal for entry in os.listdir(root): 421f750ae73SVladimir Kotal path = os.path.join(root, entry) 422f750ae73SVladimir Kotal if os.path.isdir(path): 423f750ae73SVladimir Kotal try: 424f750ae73SVladimir Kotal logger.info("Removing '{}'".format(path)) 425f750ae73SVladimir Kotal shutil.rmtree(path) 426f750ae73SVladimir Kotal except Exception as e: 427f750ae73SVladimir Kotal logger.error("cannot delete '{}': {}".format(path, e)) 428f750ae73SVladimir Kotal 429f750ae73SVladimir Kotal 430d4cbb62dSVladimir Kotaldef start_rest_thread(logger): 431d4cbb62dSVladimir Kotal rest_port = get_num_from_env(logger, 'REST_PORT', 5000) 432d4cbb62dSVladimir Kotal token = os.environ.get('REST_TOKEN') 433d4cbb62dSVladimir Kotal global expected_token 434d4cbb62dSVladimir Kotal if token: 435d4cbb62dSVladimir Kotal logger.debug("Setting expected token for REST endpoint" 436d4cbb62dSVladimir Kotal "on port {}".format(rest_port)) 437d4cbb62dSVladimir Kotal expected_token = token 438d4cbb62dSVladimir Kotal logger.debug("Starting REST thread to listen for requests " 439d4cbb62dSVladimir Kotal "on port {} on the {} endpoint". 440d4cbb62dSVladimir Kotal format(rest_port, REINDEX_POINT)) 441d4cbb62dSVladimir Kotal rest_thread = threading.Thread(target=rest_function, 442d4cbb62dSVladimir Kotal name="REST thread", 443d4cbb62dSVladimir Kotal args=(logger, rest_port), daemon=True) 444d4cbb62dSVladimir Kotal rest_thread.start() 445d4cbb62dSVladimir Kotal 446d4cbb62dSVladimir Kotal 447d4cbb62dSVladimir Kotaldef start_timeout_thread(logger, sync_period): 44886d11813SVladimir Kotal logger.debug("Starting timeout thread") 449d4cbb62dSVladimir Kotal thread = threading.Thread(target=timeout_loop, 450d4cbb62dSVladimir Kotal name="Timeout thread", 451d4cbb62dSVladimir Kotal args=(logger, sync_period), daemon=True) 452d4cbb62dSVladimir Kotal thread.start() 453d4cbb62dSVladimir Kotal 454d4cbb62dSVladimir Kotal 455f7d6d77cSVladimir Kotaldef main(): 456b2d29daeSVladimir Kotal log_level = os.environ.get('OPENGROK_LOG_LEVEL') 457b2d29daeSVladimir Kotal if log_level: 458b2d29daeSVladimir Kotal log_level = get_log_level(log_level) 459b2d29daeSVladimir Kotal else: 460b2d29daeSVladimir Kotal log_level = logging.INFO 461b2d29daeSVladimir Kotal 462b2d29daeSVladimir Kotal logger = get_console_logger(get_class_basename(), log_level) 463b2d29daeSVladimir Kotal 464f7d6d77cSVladimir Kotal uri, url_root = set_url_root(logger, os.environ.get('URL_ROOT')) 465f7d6d77cSVladimir Kotal logger.debug("URL_ROOT = {}".format(url_root)) 466f7d6d77cSVladimir Kotal logger.debug("URI = {}".format(uri)) 467b2d29daeSVladimir Kotal 46854289af4SVladimir Kotal sync_period = get_num_from_env(logger, 'SYNC_PERIOD_MINUTES', 10) 4693d5852a4SVladimir Kotal if sync_period == 0: 470b531e965SVladimir Kotal logger.info("periodic synchronization disabled") 471b2d29daeSVladimir Kotal else: 4723d5852a4SVladimir Kotal logger.info("synchronization period = {} minutes".format(sync_period)) 473b2d29daeSVladimir Kotal 474b2d29daeSVladimir Kotal # Note that deploy is done before Tomcat is started. 475f7d6d77cSVladimir Kotal deploy(logger, url_root) 476b2d29daeSVladimir Kotal 477f7d6d77cSVladimir Kotal if url_root != '/source': 478f7d6d77cSVladimir Kotal setup_redirect_source(logger, url_root) 479b2d29daeSVladimir Kotal 480b2d29daeSVladimir Kotal env = {} 481dadacd3dSAdam Hornacek extra_indexer_options = os.environ.get('INDEXER_OPT', '') 4824810eb96SVladimir Kotal if extra_indexer_options: 4834810eb96SVladimir Kotal logger.info("extra indexer options: {}".format(extra_indexer_options)) 4844810eb96SVladimir Kotal env['OPENGROK_INDEXER_OPTIONAL_ARGS'] = extra_indexer_options 485b2d29daeSVladimir Kotal if os.environ.get('NOMIRROR'): 486b2d29daeSVladimir Kotal env['OPENGROK_NO_MIRROR'] = os.environ.get('NOMIRROR') 487b2d29daeSVladimir Kotal logger.debug('Extra environment: {}'.format(env)) 488b2d29daeSVladimir Kotal 4894810eb96SVladimir Kotal use_projects = True 4904810eb96SVladimir Kotal if os.environ.get('AVOID_PROJECTS'): 4914810eb96SVladimir Kotal use_projects = False 4924810eb96SVladimir Kotal 493b2d29daeSVladimir Kotal # 494b2d29daeSVladimir Kotal # Create empty configuration to avoid the non existent file exception 495b2d29daeSVladimir Kotal # in the web app during the first web app startup. 496b2d29daeSVladimir Kotal # 497b2d29daeSVladimir Kotal if not os.path.exists(OPENGROK_CONFIG_FILE) or \ 498b2d29daeSVladimir Kotal os.path.getsize(OPENGROK_CONFIG_FILE) == 0: 49919f6e235SVladimir Kotal create_bare_config(logger, use_projects, extra_indexer_options.split()) 500b2d29daeSVladimir Kotal 501b9361335SVladimir Kotal # 502f750ae73SVladimir Kotal # Index check needs read-only configuration so it is placed 503f750ae73SVladimir Kotal # right after create_bare_config(). 504f750ae73SVladimir Kotal # 505f750ae73SVladimir Kotal check_index_and_wipe_out(logger) 506f750ae73SVladimir Kotal 507f750ae73SVladimir Kotal # 508b9361335SVladimir Kotal # If there is read-only configuration file, merge it with current 509b9361335SVladimir Kotal # configuration. 510b9361335SVladimir Kotal # 511b9361335SVladimir Kotal read_only_config_file = os.environ.get('READONLY_CONFIG_FILE') 512b9361335SVladimir Kotal if read_only_config_file and os.path.exists(read_only_config_file): 513b9361335SVladimir Kotal logger.info('Merging read-only configuration from \'{}\' with current ' 514b9361335SVladimir Kotal 'configuration in \'{}\''.format(read_only_config_file, 515b9361335SVladimir Kotal OPENGROK_CONFIG_FILE)) 516b9361335SVladimir Kotal out_file = None 517b9361335SVladimir Kotal with tempfile.NamedTemporaryFile(mode='w+', delete=False, 518b9361335SVladimir Kotal prefix='merged_config') as tmp_out: 51970e4aafbSVladimir Kotal out_file = tmp_out.name 520b9361335SVladimir Kotal merge_config_files(read_only_config_file, OPENGROK_CONFIG_FILE, 521b9361335SVladimir Kotal tmp_out, jar=OPENGROK_JAR, loglevel=log_level) 522b9361335SVladimir Kotal 523b9361335SVladimir Kotal if out_file and os.path.getsize(out_file) > 0: 524b9361335SVladimir Kotal shutil.move(tmp_out.name, OPENGROK_CONFIG_FILE) 525b9361335SVladimir Kotal else: 526b9361335SVladimir Kotal logger.warning('Failed to merge read-only configuration, ' 527b9361335SVladimir Kotal 'leaving the original in place') 52870e4aafbSVladimir Kotal if out_file: 52970e4aafbSVladimir Kotal os.remove(out_file) 530b9361335SVladimir Kotal 5314810eb96SVladimir Kotal if use_projects: 532f1835fddSVladimir Kotal num_workers = get_num_from_env(logger, 'WORKERS', 533f1835fddSVladimir Kotal multiprocessing.cpu_count()) 534b2d29daeSVladimir Kotal logger.info('Number of sync workers: {}'.format(num_workers)) 535b2d29daeSVladimir Kotal 536*941332aaSVladimir Kotal mirror_config = os.path.join(OPENGROK_CONFIG_DIR, "mirror.yml") 537*941332aaSVladimir Kotal if not os.path.exists(mirror_config): 538*941332aaSVladimir Kotal with open(mirror_config, 'w') as fp: 539*941332aaSVladimir Kotal pass 540*941332aaSVladimir Kotal 5414810eb96SVladimir Kotal worker_function = project_syncer 5424810eb96SVladimir Kotal syncer_args = (logger, log_level, uri, 543f7d6d77cSVladimir Kotal OPENGROK_CONFIG_FILE, 544d4cbb62dSVladimir Kotal num_workers, env) 5454810eb96SVladimir Kotal else: 5464810eb96SVladimir Kotal worker_function = indexer_no_projects 547d4cbb62dSVladimir Kotal syncer_args = (logger, uri, OPENGROK_CONFIG_FILE, 5484810eb96SVladimir Kotal extra_indexer_options) 5494810eb96SVladimir Kotal 5504810eb96SVladimir Kotal logger.debug("Starting sync thread") 5516100daaaSVladimir Kotal sync_thread = threading.Thread(target=worker_function, name="Sync thread", 5526100daaaSVladimir Kotal args=syncer_args, daemon=True) 5536100daaaSVladimir Kotal sync_thread.start() 554b2d29daeSVladimir Kotal 555d4cbb62dSVladimir Kotal start_rest_thread(logger) 55686d11813SVladimir Kotal if sync_period > 0: 557d4cbb62dSVladimir Kotal start_timeout_thread(logger, sync_period) 558f1835fddSVladimir Kotal 559b2d29daeSVladimir Kotal # Start Tomcat last. It will be the foreground process. 560b2d29daeSVladimir Kotal logger.info("Starting Tomcat") 5616100daaaSVladimir Kotal global tomcat_popen 5626100daaaSVladimir Kotal tomcat_popen = subprocess.Popen([os.path.join(tomcat_root, 'bin', 5636100daaaSVladimir Kotal 'catalina.sh'), 5646100daaaSVladimir Kotal 'run']) 5656100daaaSVladimir Kotal tomcat_popen.wait() 566f7d6d77cSVladimir Kotal 567f7d6d77cSVladimir Kotal 568e6daae6cSVladimir Kotaldef signal_handler(signum, frame): 56923cd7cffSVladimir Kotal print("Received signal {}".format(signum)) 57023cd7cffSVladimir Kotal 5716100daaaSVladimir Kotal global tomcat_popen 5726100daaaSVladimir Kotal print("Terminating Tomcat {}".format(tomcat_popen)) 5736100daaaSVladimir Kotal tomcat_popen.terminate() 5746100daaaSVladimir Kotal 5759e27dd5fSVladimir Kotal sys.exit(0) 576e6daae6cSVladimir Kotal 577e6daae6cSVladimir Kotal 578e6daae6cSVladimir Kotalif __name__ == "__main__": 579e6daae6cSVladimir Kotal signal.signal(signal.SIGTERM, signal_handler) 580e6daae6cSVladimir Kotal signal.signal(signal.SIGINT, signal_handler) 581e6daae6cSVladimir Kotal 582e6daae6cSVladimir Kotal main() 583