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 27b2d29daeSVladimir Kotalimport shutil 28b2d29daeSVladimir Kotalimport subprocess 29b2d29daeSVladimir Kotalimport threading 30b2d29daeSVladimir Kotalimport time 31*d44cbea0SVladimir Kotalfrom pathlib import Path 32*d44cbea0SVladimir Kotalfrom requests import get, ConnectionError 33b2d29daeSVladimir Kotal 34b2d29daeSVladimir Kotalfrom opengrok_tools.utils.log import get_console_logger, \ 35b2d29daeSVladimir Kotal get_log_level, get_class_basename 36b2d29daeSVladimir Kotalfrom opengrok_tools.deploy import deploy_war 37b2d29daeSVladimir Kotalfrom opengrok_tools.utils.indexer import Indexer 38b2d29daeSVladimir Kotalfrom opengrok_tools.sync import do_sync 39b2d29daeSVladimir Kotalfrom opengrok_tools.utils.opengrok import list_projects, \ 40b2d29daeSVladimir Kotal add_project, delete_project, get_configuration 41b2d29daeSVladimir Kotalfrom opengrok_tools.utils.readconfig import read_config 42b2d29daeSVladimir Kotalfrom opengrok_tools.utils.exitvals import SUCCESS_EXITVAL 43b2d29daeSVladimir Kotal 44b2d29daeSVladimir Kotal 45b2d29daeSVladimir Kotalfs_root = os.path.abspath('.').split(os.path.sep)[0] + os.path.sep 46b2d29daeSVladimir Kotalif os.environ.get('OPENGROK_TOMCAT_ROOT'): # debug only 47b2d29daeSVladimir Kotal tomcat_root = os.environ.get('OPENGROK_TOMCAT_ROOT') 48b2d29daeSVladimir Kotalelse: 49b2d29daeSVladimir Kotal tomcat_root = os.path.join(fs_root, "usr", "local", "tomcat") 50b2d29daeSVladimir Kotal 51b2d29daeSVladimir Kotalif os.environ.get('OPENGROK_ROOT'): # debug only 52b2d29daeSVladimir Kotal OPENGROK_BASE_DIR = os.environ.get('OPENGROK_ROOT') 53b2d29daeSVladimir Kotalelse: 54b2d29daeSVladimir Kotal OPENGROK_BASE_DIR = os.path.join(fs_root, "opengrok") 55b2d29daeSVladimir Kotal 56b2d29daeSVladimir KotalOPENGROK_LIB_DIR = os.path.join(OPENGROK_BASE_DIR, "lib") 57b2d29daeSVladimir KotalOPENGROK_DATA_ROOT = os.path.join(OPENGROK_BASE_DIR, "data") 58b2d29daeSVladimir KotalOPENGROK_SRC_ROOT = os.path.join(OPENGROK_BASE_DIR, "src") 59b2d29daeSVladimir KotalBODY_INCLUDE_FILE = os.path.join(OPENGROK_DATA_ROOT, "body_include") 60b2d29daeSVladimir KotalOPENGROK_CONFIG_FILE = os.path.join(OPENGROK_BASE_DIR, "etc", 61b2d29daeSVladimir Kotal "configuration.xml") 62b2d29daeSVladimir KotalOPENGROK_WEBAPPS_DIR = os.path.join(tomcat_root, "webapps") 63b2d29daeSVladimir Kotal 64b2d29daeSVladimir Kotal 65b2d29daeSVladimir Kotaldef set_url_root(url_root): 66b2d29daeSVladimir Kotal """ 67b2d29daeSVladimir Kotal Set URL root and URI based on input 68b2d29daeSVladimir Kotal :param url_root: input 69b2d29daeSVladimir Kotal :return: URI and URL root 70b2d29daeSVladimir Kotal """ 71b2d29daeSVladimir Kotal if not url_root: 72b2d29daeSVladimir Kotal url_root = '/' 73b2d29daeSVladimir Kotal 74b2d29daeSVladimir Kotal if ' ' in url_root: 75b2d29daeSVladimir Kotal logger.warn('Deployment path contains spaces. Deploying to root') 76b2d29daeSVladimir Kotal url_root = '/' 77b2d29daeSVladimir Kotal 78b2d29daeSVladimir Kotal # Remove leading and trailing slashes 79b2d29daeSVladimir Kotal if url_root.startswith('/'): 80b2d29daeSVladimir Kotal url_root = url_root[1:] 81b2d29daeSVladimir Kotal if url_root.endswith('/'): 82b2d29daeSVladimir Kotal url_root = url_root[:-1] 83b2d29daeSVladimir Kotal 84b2d29daeSVladimir Kotal uri = "http://localhost:8080/" + url_root 85b2d29daeSVladimir Kotal # 86b2d29daeSVladimir Kotal # Make sure URI ends with slash. This is important for the various API 87b2d29daeSVladimir Kotal # calls, notably for those that check the HTTP error code. 88b2d29daeSVladimir Kotal # Normally accessing the URI without the terminating slash results in 89b2d29daeSVladimir Kotal # HTTP redirect (code 302) instead of success (200). 90b2d29daeSVladimir Kotal # 91b2d29daeSVladimir Kotal if not uri.endswith('/'): 92b2d29daeSVladimir Kotal uri = uri + '/' 93b2d29daeSVladimir Kotal 94b2d29daeSVladimir Kotal return uri, url_root 95b2d29daeSVladimir Kotal 96b2d29daeSVladimir Kotal 97b2d29daeSVladimir Kotaldef get_war_name(url_root): 98*d44cbea0SVladimir Kotal """ 99*d44cbea0SVladimir Kotal :param url_root: web app URL root 100*d44cbea0SVladimir Kotal :return: filename of the WAR file 101*d44cbea0SVladimir Kotal """ 102b2d29daeSVladimir Kotal if len(url_root) == 0: 103b2d29daeSVladimir Kotal return "ROOT.war" 104b2d29daeSVladimir Kotal else: 105b2d29daeSVladimir Kotal return url_root + ".war" 106b2d29daeSVladimir Kotal 107b2d29daeSVladimir Kotal 108b2d29daeSVladimir Kotaldef deploy(url_root): 109b2d29daeSVladimir Kotal """ 110b2d29daeSVladimir Kotal Deploy the web application 111b2d29daeSVladimir Kotal :param url_root: web app URL root 112b2d29daeSVladimir Kotal """ 113b2d29daeSVladimir Kotal 114b2d29daeSVladimir Kotal logger.info('Deploying web application') 115b2d29daeSVladimir Kotal webapps_dir = os.path.join(tomcat_root, 'webapps') 116b2d29daeSVladimir Kotal if not os.path.isdir(webapps_dir): 117b2d29daeSVladimir Kotal raise Exception("{} is not a directory".format(webapps_dir)) 118b2d29daeSVladimir Kotal 119b2d29daeSVladimir Kotal for item in os.listdir(webapps_dir): 120b2d29daeSVladimir Kotal subdir = os.path.join(webapps_dir, item) 121b2d29daeSVladimir Kotal if os.path.isdir(subdir): 122b2d29daeSVladimir Kotal logger.debug("Removing '{}' directory recursively".format(subdir)) 123b2d29daeSVladimir Kotal shutil.rmtree(subdir) 124b2d29daeSVladimir Kotal 125b2d29daeSVladimir Kotal deploy_war(logger, os.path.join(OPENGROK_LIB_DIR, "source.war"), 126b2d29daeSVladimir Kotal os.path.join(OPENGROK_WEBAPPS_DIR, get_war_name(url_root)), 127b2d29daeSVladimir Kotal OPENGROK_CONFIG_FILE, None) 128b2d29daeSVladimir Kotal 129b2d29daeSVladimir Kotal 130b2d29daeSVladimir Kotaldef setup_redirect_source(url_root): 131b2d29daeSVladimir Kotal """ 132b2d29daeSVladimir Kotal Set up redirect from /source 133b2d29daeSVladimir Kotal """ 134b2d29daeSVladimir Kotal logger.debug("Setting up redirect from /source to '{}'".format(url_root)) 135b2d29daeSVladimir Kotal source_dir = os.path.join(OPENGROK_WEBAPPS_DIR, "source") 136b2d29daeSVladimir Kotal if not os.path.isdir(source_dir): 137b2d29daeSVladimir Kotal os.makedirs(source_dir) 138b2d29daeSVladimir Kotal 139b2d29daeSVladimir Kotal with open(os.path.join(source_dir, "index.jsp"), "w+") as index: 140b2d29daeSVladimir Kotal index.write("<% response.sendRedirect(\"/{}\"); %>".format(url_root)) 141b2d29daeSVladimir Kotal 142b2d29daeSVladimir Kotal 143b2d29daeSVladimir Kotaldef wait_for_tomcat(uri): 144b2d29daeSVladimir Kotal """ 145b2d29daeSVladimir Kotal Active/busy waiting for Tomcat to come up. 146b2d29daeSVladimir Kotal Currently there is no upper time bound. 147b2d29daeSVladimir Kotal """ 148b2d29daeSVladimir Kotal logger.info("Waiting for Tomcat to start") 149b2d29daeSVladimir Kotal 150b2d29daeSVladimir Kotal while True: 151b2d29daeSVladimir Kotal try: 152*d44cbea0SVladimir Kotal ret = get(uri) 153*d44cbea0SVladimir Kotal status = ret.status_code 154b2d29daeSVladimir Kotal except ConnectionError: 155b2d29daeSVladimir Kotal status = 0 156b2d29daeSVladimir Kotal 157b2d29daeSVladimir Kotal if status != 200: 158b2d29daeSVladimir Kotal logger.debug("Got status {} for {}, sleeping for 1 second". 159b2d29daeSVladimir Kotal format(status, uri)) 160b2d29daeSVladimir Kotal time.sleep(1) 161b2d29daeSVladimir Kotal else: 162b2d29daeSVladimir Kotal break 163b2d29daeSVladimir Kotal 164b2d29daeSVladimir Kotal logger.info("Tomcat is ready") 165b2d29daeSVladimir Kotal 166b2d29daeSVladimir Kotal 167b2d29daeSVladimir Kotaldef refresh_projects(uri): 168b2d29daeSVladimir Kotal """ 169b2d29daeSVladimir Kotal Ensure each immediate source root subdirectory is a project. 170b2d29daeSVladimir Kotal """ 171b2d29daeSVladimir Kotal webapp_projects = list_projects(logger, uri) 172b2d29daeSVladimir Kotal logger.debug('Projects from the web app: {}'.format(webapp_projects)) 173b2d29daeSVladimir Kotal src_root = OPENGROK_SRC_ROOT 174b2d29daeSVladimir Kotal 175b2d29daeSVladimir Kotal # Add projects. 176b2d29daeSVladimir Kotal for item in os.listdir(src_root): 177b2d29daeSVladimir Kotal logger.debug('Got item {}'.format(item)) 178b2d29daeSVladimir Kotal if os.path.isdir(os.path.join(src_root, item)): 179b2d29daeSVladimir Kotal if item not in webapp_projects: 180b2d29daeSVladimir Kotal logger.info("Adding project {}".format(item)) 181b2d29daeSVladimir Kotal add_project(logger, item, uri) 182b2d29daeSVladimir Kotal 183b2d29daeSVladimir Kotal # Remove projects 184b2d29daeSVladimir Kotal for item in webapp_projects: 185b2d29daeSVladimir Kotal if not os.path.isdir(os.path.join(src_root, item)): 186b2d29daeSVladimir Kotal logger.info("Deleting project {}".format(item)) 187b2d29daeSVladimir Kotal delete_project(logger, item, uri) 188b2d29daeSVladimir Kotal 189b2d29daeSVladimir Kotal 190b2d29daeSVladimir Kotaldef save_config(uri, config_path): 191b2d29daeSVladimir Kotal """ 192b2d29daeSVladimir Kotal Retrieve configuration from the web app and write it to file. 193b2d29daeSVladimir Kotal :param uri: web app URI 194b2d29daeSVladimir Kotal :param config_path: file path 195b2d29daeSVladimir Kotal """ 196b2d29daeSVladimir Kotal 197b2d29daeSVladimir Kotal logger.info('Saving configuration to {}'.format(config_path)) 198b2d29daeSVladimir Kotal config = get_configuration(logger, uri) 199b2d29daeSVladimir Kotal with open(config_path, "w+") as config_file: 200b2d29daeSVladimir Kotal config_file.write(config) 201b2d29daeSVladimir Kotal 202b2d29daeSVladimir Kotal 203b2d29daeSVladimir Kotaldef merge_commands_env(commands, env): 204b2d29daeSVladimir Kotal """ 205b2d29daeSVladimir Kotal Merge environment into command structure. If any of the commands has 206b2d29daeSVladimir Kotal an environment already set, the env is merged in. 207b2d29daeSVladimir Kotal :param commands: commands structure 208b2d29daeSVladimir Kotal :param env: environment dictionary 209b2d29daeSVladimir Kotal :return: updated commands structure 210b2d29daeSVladimir Kotal """ 211b2d29daeSVladimir Kotal for cmd in commands: 212b2d29daeSVladimir Kotal cmd_env = cmd.get('env') 213b2d29daeSVladimir Kotal if cmd_env: 214b2d29daeSVladimir Kotal cmd.env.update(env) 215b2d29daeSVladimir Kotal else: 216b2d29daeSVladimir Kotal cmd['env'] = env 217b2d29daeSVladimir Kotal 218b2d29daeSVladimir Kotal return commands 219b2d29daeSVladimir Kotal 220b2d29daeSVladimir Kotal 221b2d29daeSVladimir Kotaldef syncer(loglevel, uri, config_path, reindex, numworkers, env): 222b2d29daeSVladimir Kotal """ 223b2d29daeSVladimir Kotal Wrapper for running opengrok-sync. 224b2d29daeSVladimir Kotal To be run in a thread/process in the background. 225b2d29daeSVladimir Kotal """ 226b2d29daeSVladimir Kotal 227b2d29daeSVladimir Kotal wait_for_tomcat(uri) 228b2d29daeSVladimir Kotal 229b2d29daeSVladimir Kotal while True: 230b2d29daeSVladimir Kotal refresh_projects(uri) 231b2d29daeSVladimir Kotal 232b2d29daeSVladimir Kotal if os.environ.get('OPENGROK_SYNC_YML'): # debug only 233b2d29daeSVladimir Kotal config_file = os.environ.get('OPENGROK_SYNC_YML') 234b2d29daeSVladimir Kotal else: 235b2d29daeSVladimir Kotal config_file = os.path.join(fs_root, 'scripts', 'sync.yml') 236b2d29daeSVladimir Kotal config = read_config(logger, config_file) 237b2d29daeSVladimir Kotal if config is None: 238b2d29daeSVladimir Kotal logger.error("Cannot read config file from {}".format(config_file)) 239b2d29daeSVladimir Kotal raise Exception("no sync config") 240b2d29daeSVladimir Kotal 241b2d29daeSVladimir Kotal projects = list_projects(logger, uri) 242b2d29daeSVladimir Kotal # 243b2d29daeSVladimir Kotal # The driveon=True is needed for the initial indexing of newly 244b2d29daeSVladimir Kotal # added project, otherwise the incoming check in the opengrok-mirror 245b2d29daeSVladimir Kotal # would short circuit it. 246b2d29daeSVladimir Kotal # 247b2d29daeSVladimir Kotal if env: 248b2d29daeSVladimir Kotal logger.info('Merging commands with environment') 249b2d29daeSVladimir Kotal commands = merge_commands_env(config["commands"], env) 250b2d29daeSVladimir Kotal logger.debug(config['commands']) 251b2d29daeSVladimir Kotal else: 252b2d29daeSVladimir Kotal commands = config["commands"] 253b2d29daeSVladimir Kotal 254b2d29daeSVladimir Kotal logger.info("Sync starting") 255b2d29daeSVladimir Kotal do_sync(loglevel, commands, config.get('cleanup'), 256b2d29daeSVladimir Kotal projects, config.get("ignore_errors"), uri, 257b2d29daeSVladimir Kotal numworkers, driveon=True, logger=logger, print_output=True) 258b2d29daeSVladimir Kotal logger.info("Sync done") 259b2d29daeSVladimir Kotal 260b2d29daeSVladimir Kotal # Workaround for https://github.com/oracle/opengrok/issues/1670 261b2d29daeSVladimir Kotal Path(os.path.join(OPENGROK_DATA_ROOT, 'timestamp')).touch() 262b2d29daeSVladimir Kotal 263b2d29daeSVladimir Kotal save_config(uri, config_path) 264b2d29daeSVladimir Kotal 265b2d29daeSVladimir Kotal sleep_seconds = int(reindex) * 60 266b2d29daeSVladimir Kotal logger.info("Sleeping for {} seconds".format(sleep_seconds)) 267b2d29daeSVladimir Kotal time.sleep(sleep_seconds) 268b2d29daeSVladimir Kotal 269b2d29daeSVladimir Kotal 270b2d29daeSVladimir Kotaldef create_bare_config(): 271b2d29daeSVladimir Kotal """ 272b2d29daeSVladimir Kotal Create bare configuration file with a few basic settings. 273b2d29daeSVladimir Kotal """ 274b2d29daeSVladimir Kotal 275b2d29daeSVladimir Kotal logger.info('Creating bare configuration in {}'. 276b2d29daeSVladimir Kotal format(OPENGROK_CONFIG_FILE)) 277b2d29daeSVladimir Kotal indexer = Indexer(['-s', OPENGROK_SRC_ROOT, 278b2d29daeSVladimir Kotal '-d', OPENGROK_DATA_ROOT, 279b2d29daeSVladimir Kotal '-c', '/usr/local/bin/ctags', 280b2d29daeSVladimir Kotal '--remote', 'on', 281b2d29daeSVladimir Kotal '-P', '-H', 282b2d29daeSVladimir Kotal '-W', OPENGROK_CONFIG_FILE, 283b2d29daeSVladimir Kotal '--noIndex'], 284b2d29daeSVladimir Kotal jar=os.path.join(OPENGROK_LIB_DIR, 285b2d29daeSVladimir Kotal 'opengrok.jar'), 286b2d29daeSVladimir Kotal logger=logger, doprint=True) 287b2d29daeSVladimir Kotal indexer.execute() 288b2d29daeSVladimir Kotal ret = indexer.getretcode() 289b2d29daeSVladimir Kotal if ret != SUCCESS_EXITVAL: 290b2d29daeSVladimir Kotal logger.error('Command returned {}'.format(ret)) 291b2d29daeSVladimir Kotal logger.error(indexer.geterroutput()) 292b2d29daeSVladimir Kotal raise Exception("Failed to create bare configuration") 293b2d29daeSVladimir Kotal 294b2d29daeSVladimir Kotal 295b2d29daeSVladimir Kotalif __name__ == "__main__": 296b2d29daeSVladimir Kotal log_level = os.environ.get('OPENGROK_LOG_LEVEL') 297b2d29daeSVladimir Kotal if log_level: 298b2d29daeSVladimir Kotal log_level = get_log_level(log_level) 299b2d29daeSVladimir Kotal else: 300b2d29daeSVladimir Kotal log_level = logging.INFO 301b2d29daeSVladimir Kotal 302b2d29daeSVladimir Kotal logger = get_console_logger(get_class_basename(), log_level) 303b2d29daeSVladimir Kotal 304b2d29daeSVladimir Kotal URI, URL_ROOT = set_url_root(os.environ.get('URL_ROOT')) 305b2d29daeSVladimir Kotal logger.debug("URL_ROOT = {}".format(URL_ROOT)) 306b2d29daeSVladimir Kotal logger.debug("URI = {}".format(URI)) 307b2d29daeSVladimir Kotal 308b2d29daeSVladimir Kotal # default period for reindexing (in minutes) 309b2d29daeSVladimir Kotal reindex_env = os.environ.get('REINDEX') 310b2d29daeSVladimir Kotal if reindex_env: 311b2d29daeSVladimir Kotal reindex_min = reindex_env 312b2d29daeSVladimir Kotal else: 313b2d29daeSVladimir Kotal reindex_min = 10 314b2d29daeSVladimir Kotal logger.debug("reindex period = {} minutes".format(reindex_min)) 315b2d29daeSVladimir Kotal 316b2d29daeSVladimir Kotal # Note that deploy is done before Tomcat is started. 317b2d29daeSVladimir Kotal deploy(URL_ROOT) 318b2d29daeSVladimir Kotal 319b2d29daeSVladimir Kotal if URL_ROOT != '/source': 320b2d29daeSVladimir Kotal setup_redirect_source(URL_ROOT) 321b2d29daeSVladimir Kotal 322b2d29daeSVladimir Kotal env = {} 323b2d29daeSVladimir Kotal if os.environ.get('INDEXER_OPT'): 324b2d29daeSVladimir Kotal env['OPENGROK_INDEXER_OPTIONAL_ARGS'] = \ 325b2d29daeSVladimir Kotal os.environ.get('INDEXER_OPT') 326b2d29daeSVladimir Kotal if os.environ.get('NOMIRROR'): 327b2d29daeSVladimir Kotal env['OPENGROK_NO_MIRROR'] = os.environ.get('NOMIRROR') 328b2d29daeSVladimir Kotal logger.debug('Extra environment: {}'.format(env)) 329b2d29daeSVladimir Kotal 330b2d29daeSVladimir Kotal # 331b2d29daeSVladimir Kotal # Create empty configuration to avoid the non existent file exception 332b2d29daeSVladimir Kotal # in the web app during the first web app startup. 333b2d29daeSVladimir Kotal # 334b2d29daeSVladimir Kotal if not os.path.exists(OPENGROK_CONFIG_FILE) or \ 335b2d29daeSVladimir Kotal os.path.getsize(OPENGROK_CONFIG_FILE) == 0: 336b2d29daeSVladimir Kotal create_bare_config() 337b2d29daeSVladimir Kotal 338b2d29daeSVladimir Kotal if os.environ.get('WORKERS'): 339b2d29daeSVladimir Kotal num_workers = os.environ.get('WORKERS') 340b2d29daeSVladimir Kotal else: 341b2d29daeSVladimir Kotal num_workers = multiprocessing.cpu_count() 342b2d29daeSVladimir Kotal logger.info('Number of sync workers: {}'.format(num_workers)) 343b2d29daeSVladimir Kotal 344b2d29daeSVladimir Kotal logger.debug("Starting sync thread") 345b2d29daeSVladimir Kotal t = threading.Thread(target=syncer, name="Sync thread", 346b2d29daeSVladimir Kotal args=(log_level, URI, OPENGROK_CONFIG_FILE, 347b2d29daeSVladimir Kotal reindex_min, num_workers, env)) 348b2d29daeSVladimir Kotal t.start() 349b2d29daeSVladimir Kotal 350b2d29daeSVladimir Kotal # Start Tomcat last. It will be the foreground process. 351b2d29daeSVladimir Kotal logger.info("Starting Tomcat") 352b2d29daeSVladimir Kotal subprocess.run([os.path.join(tomcat_root, 'bin', 'catalina.sh'), 'run']) 353