xref: /OpenGrok/docker/start.py (revision cec5eb3a12c17a5cf918d075783c6f9c66006cf1)
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
51*cec5eb3aSVladimir Kotalfrom opengrok_tools.mirror import OPENGROK_NO_MIRROR_ENV
52b2d29daeSVladimir Kotal
53b2d29daeSVladimir Kotal
54b2d29daeSVladimir Kotalfs_root = os.path.abspath('.').split(os.path.sep)[0] + os.path.sep
55b2d29daeSVladimir Kotalif os.environ.get('OPENGROK_TOMCAT_ROOT'):  # debug only
56b2d29daeSVladimir Kotal    tomcat_root = os.environ.get('OPENGROK_TOMCAT_ROOT')
57b2d29daeSVladimir Kotalelse:
58b2d29daeSVladimir Kotal    tomcat_root = os.path.join(fs_root, "usr", "local", "tomcat")
59b2d29daeSVladimir Kotal
60b2d29daeSVladimir Kotalif os.environ.get('OPENGROK_ROOT'):  # debug only
61b2d29daeSVladimir Kotal    OPENGROK_BASE_DIR = os.environ.get('OPENGROK_ROOT')
62b2d29daeSVladimir Kotalelse:
63b2d29daeSVladimir Kotal    OPENGROK_BASE_DIR = os.path.join(fs_root, "opengrok")
64b2d29daeSVladimir Kotal
65b2d29daeSVladimir KotalOPENGROK_LIB_DIR = os.path.join(OPENGROK_BASE_DIR, "lib")
66b2d29daeSVladimir KotalOPENGROK_DATA_ROOT = os.path.join(OPENGROK_BASE_DIR, "data")
67b2d29daeSVladimir KotalOPENGROK_SRC_ROOT = os.path.join(OPENGROK_BASE_DIR, "src")
68b2d29daeSVladimir KotalBODY_INCLUDE_FILE = os.path.join(OPENGROK_DATA_ROOT, "body_include")
69941332aaSVladimir KotalOPENGROK_CONFIG_DIR = os.path.join(OPENGROK_BASE_DIR, "etc")
70941332aaSVladimir KotalOPENGROK_CONFIG_FILE = os.path.join(OPENGROK_CONFIG_DIR,
71b2d29daeSVladimir Kotal                                    "configuration.xml")
72b2d29daeSVladimir KotalOPENGROK_WEBAPPS_DIR = os.path.join(tomcat_root, "webapps")
734810eb96SVladimir KotalOPENGROK_JAR = os.path.join(OPENGROK_LIB_DIR, 'opengrok.jar')
74b2d29daeSVladimir Kotal
755036d900SVladimir KotalNOMIRROR_ENV_NAME = 'NOMIRROR'
765036d900SVladimir Kotal
77f1835fddSVladimir Kotalexpected_token = None
78f1835fddSVladimir Kotal
79f1835fddSVladimir Kotalsleep_event = threading.Event()
80f1835fddSVladimir Kotalapp = Flask(__name__)
81f1835fddSVladimir Kotalauth = HTTPTokenAuth(scheme='Bearer')
824990cf90SVladimir KotalREINDEX_POINT = '/reindex'
83f1835fddSVladimir Kotal
84f1835fddSVladimir Kotal
85d4cbb62dSVladimir Kotaldef trigger_reindex():
86d4cbb62dSVladimir Kotal    # Signal the sync/indexer thread.
87d4cbb62dSVladimir Kotal    sleep_event.set()
88d4cbb62dSVladimir Kotal    sleep_event.clear()
89d4cbb62dSVladimir Kotal
90d4cbb62dSVladimir Kotal
91f1835fddSVladimir Kotal@auth.verify_token
92f1835fddSVladimir Kotaldef verify_token(token):
93f1835fddSVladimir Kotal    if expected_token is None:
94f1835fddSVladimir Kotal        return "yes"
95f1835fddSVladimir Kotal
96f1835fddSVladimir Kotal    if token is not None and token == expected_token:
97f1835fddSVladimir Kotal        return "yes"
98f1835fddSVladimir Kotal
99f1835fddSVladimir Kotal
1004990cf90SVladimir Kotal@app.route(REINDEX_POINT)
101f1835fddSVladimir Kotal@auth.login_required
102f1835fddSVladimir Kotaldef index():
103d4cbb62dSVladimir Kotal    trigger_reindex()
104f1835fddSVladimir Kotal
105f1835fddSVladimir Kotal    return "Reindex triggered"
106f1835fddSVladimir Kotal
107f1835fddSVladimir Kotal
108f1835fddSVladimir Kotaldef rest_function(logger, rest_port):
109f1835fddSVladimir Kotal    logger.info("Starting REST app on port {}".format(rest_port))
110f1835fddSVladimir Kotal    serve(app, host="0.0.0.0", port=rest_port)
111f1835fddSVladimir Kotal
112b2d29daeSVladimir Kotal
113f7d6d77cSVladimir Kotaldef set_url_root(logger, url_root):
114b2d29daeSVladimir Kotal    """
115b2d29daeSVladimir Kotal    Set URL root and URI based on input
116788f14d3SVladimir Kotal    :param logger: logger instance
117b2d29daeSVladimir Kotal    :param url_root: input
118b2d29daeSVladimir Kotal    :return: URI and URL root
119b2d29daeSVladimir Kotal    """
120b2d29daeSVladimir Kotal    if not url_root:
121b2d29daeSVladimir Kotal        url_root = '/'
122b2d29daeSVladimir Kotal
123b2d29daeSVladimir Kotal    if ' ' in url_root:
124b2d29daeSVladimir Kotal        logger.warn('Deployment path contains spaces. Deploying to root')
125b2d29daeSVladimir Kotal        url_root = '/'
126b2d29daeSVladimir Kotal
127b2d29daeSVladimir Kotal    # Remove leading and trailing slashes
128b2d29daeSVladimir Kotal    if url_root.startswith('/'):
129b2d29daeSVladimir Kotal        url_root = url_root[1:]
130b2d29daeSVladimir Kotal    if url_root.endswith('/'):
131b2d29daeSVladimir Kotal        url_root = url_root[:-1]
132b2d29daeSVladimir Kotal
133b2d29daeSVladimir Kotal    uri = "http://localhost:8080/" + url_root
134b2d29daeSVladimir Kotal    #
135b2d29daeSVladimir Kotal    # Make sure URI ends with slash. This is important for the various API
136b2d29daeSVladimir Kotal    # calls, notably for those that check the HTTP error code.
137b2d29daeSVladimir Kotal    # Normally accessing the URI without the terminating slash results in
138b2d29daeSVladimir Kotal    # HTTP redirect (code 302) instead of success (200).
139b2d29daeSVladimir Kotal    #
140b2d29daeSVladimir Kotal    if not uri.endswith('/'):
141b2d29daeSVladimir Kotal        uri = uri + '/'
142b2d29daeSVladimir Kotal
143b2d29daeSVladimir Kotal    return uri, url_root
144b2d29daeSVladimir Kotal
145b2d29daeSVladimir Kotal
146b2d29daeSVladimir Kotaldef get_war_name(url_root):
147d44cbea0SVladimir Kotal    """
148d44cbea0SVladimir Kotal    :param url_root: web app URL root
149d44cbea0SVladimir Kotal    :return: filename of the WAR file
150d44cbea0SVladimir Kotal    """
151b2d29daeSVladimir Kotal    if len(url_root) == 0:
152b2d29daeSVladimir Kotal        return "ROOT.war"
153f7d6d77cSVladimir Kotal
154b2d29daeSVladimir Kotal    return url_root + ".war"
155b2d29daeSVladimir Kotal
156b2d29daeSVladimir Kotal
157f7d6d77cSVladimir Kotaldef deploy(logger, url_root):
158b2d29daeSVladimir Kotal    """
159b2d29daeSVladimir Kotal    Deploy the web application
160788f14d3SVladimir Kotal    :param logger: logger instance
161b2d29daeSVladimir Kotal    :param url_root: web app URL root
162b2d29daeSVladimir Kotal    """
163b2d29daeSVladimir Kotal
164b2d29daeSVladimir Kotal    logger.info('Deploying web application')
165b2d29daeSVladimir Kotal    webapps_dir = os.path.join(tomcat_root, 'webapps')
166b2d29daeSVladimir Kotal    if not os.path.isdir(webapps_dir):
167b2d29daeSVladimir Kotal        raise Exception("{} is not a directory".format(webapps_dir))
168b2d29daeSVladimir Kotal
169b2d29daeSVladimir Kotal    for item in os.listdir(webapps_dir):
170b2d29daeSVladimir Kotal        subdir = os.path.join(webapps_dir, item)
171b2d29daeSVladimir Kotal        if os.path.isdir(subdir):
172b2d29daeSVladimir Kotal            logger.debug("Removing '{}' directory recursively".format(subdir))
173b2d29daeSVladimir Kotal            shutil.rmtree(subdir)
174b2d29daeSVladimir Kotal
175b2d29daeSVladimir Kotal    deploy_war(logger, os.path.join(OPENGROK_LIB_DIR, "source.war"),
176b2d29daeSVladimir Kotal               os.path.join(OPENGROK_WEBAPPS_DIR, get_war_name(url_root)),
177b2d29daeSVladimir Kotal               OPENGROK_CONFIG_FILE, None)
178b2d29daeSVladimir Kotal
179b2d29daeSVladimir Kotal
180f7d6d77cSVladimir Kotaldef setup_redirect_source(logger, url_root):
181b2d29daeSVladimir Kotal    """
182b2d29daeSVladimir Kotal    Set up redirect from /source
183b2d29daeSVladimir Kotal    """
184b2d29daeSVladimir Kotal    logger.debug("Setting up redirect from /source to '{}'".format(url_root))
185b2d29daeSVladimir Kotal    source_dir = os.path.join(OPENGROK_WEBAPPS_DIR, "source")
186b2d29daeSVladimir Kotal    if not os.path.isdir(source_dir):
187b2d29daeSVladimir Kotal        os.makedirs(source_dir)
188b2d29daeSVladimir Kotal
189b2d29daeSVladimir Kotal    with open(os.path.join(source_dir, "index.jsp"), "w+") as index:
190b2d29daeSVladimir Kotal        index.write("<% response.sendRedirect(\"/{}\"); %>".format(url_root))
191b2d29daeSVladimir Kotal
192b2d29daeSVladimir Kotal
193f7d6d77cSVladimir Kotaldef wait_for_tomcat(logger, uri):
194b2d29daeSVladimir Kotal    """
195b2d29daeSVladimir Kotal    Active/busy waiting for Tomcat to come up.
196b2d29daeSVladimir Kotal    Currently there is no upper time bound.
197b2d29daeSVladimir Kotal    """
198b2d29daeSVladimir Kotal    logger.info("Waiting for Tomcat to start")
199b2d29daeSVladimir Kotal
200b2d29daeSVladimir Kotal    while True:
201b2d29daeSVladimir Kotal        try:
202d44cbea0SVladimir Kotal            ret = get(uri)
203d44cbea0SVladimir Kotal            status = ret.status_code
204b2d29daeSVladimir Kotal        except ConnectionError:
205b2d29daeSVladimir Kotal            status = 0
206b2d29daeSVladimir Kotal
207b2d29daeSVladimir Kotal        if status != 200:
208b2d29daeSVladimir Kotal            logger.debug("Got status {} for {}, sleeping for 1 second".
209b2d29daeSVladimir Kotal                         format(status, uri))
210b2d29daeSVladimir Kotal            time.sleep(1)
211b2d29daeSVladimir Kotal        else:
212b2d29daeSVladimir Kotal            break
213b2d29daeSVladimir Kotal
214b2d29daeSVladimir Kotal    logger.info("Tomcat is ready")
215b2d29daeSVladimir Kotal
216b2d29daeSVladimir Kotal
217f7d6d77cSVladimir Kotaldef refresh_projects(logger, uri):
218b2d29daeSVladimir Kotal    """
219b2d29daeSVladimir Kotal    Ensure each immediate source root subdirectory is a project.
220b2d29daeSVladimir Kotal    """
221b2d29daeSVladimir Kotal    webapp_projects = list_projects(logger, uri)
2226100daaaSVladimir Kotal    if not webapp_projects:
2236100daaaSVladimir Kotal        return
2246100daaaSVladimir Kotal
225b2d29daeSVladimir Kotal    logger.debug('Projects from the web app: {}'.format(webapp_projects))
226b2d29daeSVladimir Kotal    src_root = OPENGROK_SRC_ROOT
227b2d29daeSVladimir Kotal
228b2d29daeSVladimir Kotal    # Add projects.
229b2d29daeSVladimir Kotal    for item in os.listdir(src_root):
230b2d29daeSVladimir Kotal        logger.debug('Got item {}'.format(item))
231b2d29daeSVladimir Kotal        if os.path.isdir(os.path.join(src_root, item)):
232b2d29daeSVladimir Kotal            if item not in webapp_projects:
233b2d29daeSVladimir Kotal                logger.info("Adding project {}".format(item))
234b2d29daeSVladimir Kotal                add_project(logger, item, uri)
235b2d29daeSVladimir Kotal
236b2d29daeSVladimir Kotal    # Remove projects
237b2d29daeSVladimir Kotal    for item in webapp_projects:
238b2d29daeSVladimir Kotal        if not os.path.isdir(os.path.join(src_root, item)):
239b2d29daeSVladimir Kotal            logger.info("Deleting project {}".format(item))
240b2d29daeSVladimir Kotal            delete_project(logger, item, uri)
241b2d29daeSVladimir Kotal
242b2d29daeSVladimir Kotal
243f7d6d77cSVladimir Kotaldef save_config(logger, uri, config_path):
244b2d29daeSVladimir Kotal    """
245b2d29daeSVladimir Kotal    Retrieve configuration from the web app and write it to file.
246788f14d3SVladimir Kotal    :param logger: logger instance
247b2d29daeSVladimir Kotal    :param uri: web app URI
248b2d29daeSVladimir Kotal    :param config_path: file path
249b2d29daeSVladimir Kotal    """
250b2d29daeSVladimir Kotal
251b2d29daeSVladimir Kotal    config = get_configuration(logger, uri)
252c3904b2eSVladimir Kotal    if config is None:
253c3904b2eSVladimir Kotal        return
254c3904b2eSVladimir Kotal
255c3904b2eSVladimir Kotal    logger.info('Saving configuration to {}'.format(config_path))
256b2d29daeSVladimir Kotal    with open(config_path, "w+") as config_file:
257b2d29daeSVladimir Kotal        config_file.write(config)
258b2d29daeSVladimir Kotal
259b2d29daeSVladimir Kotal
260b2d29daeSVladimir Kotaldef merge_commands_env(commands, env):
261b2d29daeSVladimir Kotal    """
262b2d29daeSVladimir Kotal    Merge environment into command structure. If any of the commands has
263b2d29daeSVladimir Kotal    an environment already set, the env is merged in.
264b2d29daeSVladimir Kotal    :param commands: commands structure
265b2d29daeSVladimir Kotal    :param env: environment dictionary
266b2d29daeSVladimir Kotal    :return: updated commands structure
267b2d29daeSVladimir Kotal    """
268b2d29daeSVladimir Kotal    for cmd in commands:
269b2d29daeSVladimir Kotal        cmd_env = cmd.get('env')
270b2d29daeSVladimir Kotal        if cmd_env:
271b2d29daeSVladimir Kotal            cmd.env.update(env)
272b2d29daeSVladimir Kotal        else:
273b2d29daeSVladimir Kotal            cmd['env'] = env
274b2d29daeSVladimir Kotal
275b2d29daeSVladimir Kotal    return commands
276b2d29daeSVladimir Kotal
277b2d29daeSVladimir Kotal
278d4cbb62dSVladimir Kotaldef indexer_no_projects(logger, uri, config_path, extra_indexer_options):
2794810eb96SVladimir Kotal    """
2804810eb96SVladimir Kotal    Project less indexer
2814810eb96SVladimir Kotal    """
2824810eb96SVladimir Kotal
2834810eb96SVladimir Kotal    wait_for_tomcat(logger, uri)
2844810eb96SVladimir Kotal
2854810eb96SVladimir Kotal    while True:
2864810eb96SVladimir Kotal        indexer_options = ['-s', OPENGROK_SRC_ROOT,
2874810eb96SVladimir Kotal                           '-d', OPENGROK_DATA_ROOT,
2884810eb96SVladimir Kotal                           '-c', '/usr/local/bin/ctags',
2894810eb96SVladimir Kotal                           '--remote', 'on',
2904810eb96SVladimir Kotal                           '-H',
2914810eb96SVladimir Kotal                           '-W', config_path,
2924810eb96SVladimir Kotal                           '-U', uri]
2934810eb96SVladimir Kotal        if extra_indexer_options:
2944810eb96SVladimir Kotal            logger.debug("Adding extra indexer options: {}".
2954810eb96SVladimir Kotal                         format(extra_indexer_options))
2964810eb96SVladimir Kotal            indexer_options.extend(extra_indexer_options.split())
2974810eb96SVladimir Kotal        indexer = Indexer(indexer_options, logger=logger,
298d2f093daSVladimir Kotal                          jar=OPENGROK_JAR, doprint=True)
2994810eb96SVladimir Kotal        indexer.execute()
3004810eb96SVladimir Kotal
301d4cbb62dSVladimir Kotal        logger.info("Waiting for reindex to be triggered")
302b531e965SVladimir Kotal        sleep_event.wait()
3034810eb96SVladimir Kotal
3044810eb96SVladimir Kotal
305d4cbb62dSVladimir Kotaldef timeout_loop(logger, sync_period):
306d4cbb62dSVladimir Kotal    while True:
307d4cbb62dSVladimir Kotal        sleep_seconds = sync_period * 60
308d4cbb62dSVladimir Kotal        logger.info("Sleeping for {} seconds".format(sleep_seconds))
309d4cbb62dSVladimir Kotal        time.sleep(sleep_seconds)
310d4cbb62dSVladimir Kotal
311d4cbb62dSVladimir Kotal        trigger_reindex()
312d4cbb62dSVladimir Kotal
313d4cbb62dSVladimir Kotal
314d4cbb62dSVladimir Kotaldef project_syncer(logger, loglevel, uri, config_path, numworkers, env):
315b2d29daeSVladimir Kotal    """
316b2d29daeSVladimir Kotal    Wrapper for running opengrok-sync.
317b2d29daeSVladimir Kotal    To be run in a thread/process in the background.
318b2d29daeSVladimir Kotal    """
319b2d29daeSVladimir Kotal
320f7d6d77cSVladimir Kotal    wait_for_tomcat(logger, uri)
321b2d29daeSVladimir Kotal
322b2d29daeSVladimir Kotal    while True:
323f7d6d77cSVladimir Kotal        refresh_projects(logger, uri)
324b2d29daeSVladimir Kotal
325b2d29daeSVladimir Kotal        if os.environ.get('OPENGROK_SYNC_YML'):  # debug only
326b2d29daeSVladimir Kotal            config_file = os.environ.get('OPENGROK_SYNC_YML')
327b2d29daeSVladimir Kotal        else:
328b2d29daeSVladimir Kotal            config_file = os.path.join(fs_root, 'scripts', 'sync.yml')
329b2d29daeSVladimir Kotal        config = read_config(logger, config_file)
330b2d29daeSVladimir Kotal        if config is None:
331b2d29daeSVladimir Kotal            logger.error("Cannot read config file from {}".format(config_file))
332b2d29daeSVladimir Kotal            raise Exception("no sync config")
333b2d29daeSVladimir Kotal
334b2d29daeSVladimir Kotal        projects = list_projects(logger, uri)
3356100daaaSVladimir Kotal        if projects:
336b2d29daeSVladimir Kotal            #
337b2d29daeSVladimir Kotal            # The driveon=True is needed for the initial indexing of newly
3386100daaaSVladimir Kotal            # added project, otherwise the incoming check in the
3396100daaaSVladimir Kotal            # opengrok-mirror program would short circuit it.
340b2d29daeSVladimir Kotal            #
341b2d29daeSVladimir Kotal            if env:
342b2d29daeSVladimir Kotal                logger.info('Merging commands with environment')
343b2d29daeSVladimir Kotal                commands = merge_commands_env(config["commands"], env)
344b2d29daeSVladimir Kotal                logger.debug(config['commands'])
345b2d29daeSVladimir Kotal            else:
346b2d29daeSVladimir Kotal                commands = config["commands"]
347b2d29daeSVladimir Kotal
348b2d29daeSVladimir Kotal            logger.info("Sync starting")
349b2d29daeSVladimir Kotal            do_sync(loglevel, commands, config.get('cleanup'),
350b2d29daeSVladimir Kotal                    projects, config.get("ignore_errors"), uri,
351b2d29daeSVladimir Kotal                    numworkers, driveon=True, logger=logger, print_output=True)
352b2d29daeSVladimir Kotal            logger.info("Sync done")
353b2d29daeSVladimir Kotal
354b2d29daeSVladimir Kotal            # Workaround for https://github.com/oracle/opengrok/issues/1670
355b2d29daeSVladimir Kotal            Path(os.path.join(OPENGROK_DATA_ROOT, 'timestamp')).touch()
356b2d29daeSVladimir Kotal
357f7d6d77cSVladimir Kotal            save_config(logger, uri, config_path)
358b2d29daeSVladimir Kotal
359d4cbb62dSVladimir Kotal        logger.info("Waiting for reindex to be triggered")
360b531e965SVladimir Kotal        sleep_event.wait()
361b2d29daeSVladimir Kotal
362b2d29daeSVladimir Kotal
36319f6e235SVladimir Kotaldef create_bare_config(logger, use_projects, extra_indexer_options=None):
364b2d29daeSVladimir Kotal    """
365b2d29daeSVladimir Kotal    Create bare configuration file with a few basic settings.
366b2d29daeSVladimir Kotal    """
367b2d29daeSVladimir Kotal
368b2d29daeSVladimir Kotal    logger.info('Creating bare configuration in {}'.
369b2d29daeSVladimir Kotal                format(OPENGROK_CONFIG_FILE))
3704810eb96SVladimir Kotal    indexer_options = ['-s', OPENGROK_SRC_ROOT,
371b2d29daeSVladimir Kotal                       '-d', OPENGROK_DATA_ROOT,
372b2d29daeSVladimir Kotal                       '-c', '/usr/local/bin/ctags',
373b2d29daeSVladimir Kotal                       '--remote', 'on',
3744810eb96SVladimir Kotal                       '-H',
37519f6e235SVladimir Kotal                       '-S',
376b2d29daeSVladimir Kotal                       '-W', OPENGROK_CONFIG_FILE,
3774810eb96SVladimir Kotal                       '--noIndex']
3784810eb96SVladimir Kotal
3799d6d7c28SVladimir Kotal    if extra_indexer_options:
3805b279385SVladimir Kotal        if type(extra_indexer_options) is not list:
3815b279385SVladimir Kotal            raise Exception("extra_indexer_options has to be a list")
3829d6d7c28SVladimir Kotal        indexer_options.extend(extra_indexer_options)
38319f6e235SVladimir Kotal    if use_projects:
38419f6e235SVladimir Kotal        indexer_options.append('-P')
3854810eb96SVladimir Kotal    indexer = Indexer(indexer_options,
3864810eb96SVladimir Kotal                      jar=OPENGROK_JAR,
387b2d29daeSVladimir Kotal                      logger=logger, doprint=True)
388b2d29daeSVladimir Kotal    indexer.execute()
389b2d29daeSVladimir Kotal    ret = indexer.getretcode()
390b2d29daeSVladimir Kotal    if ret != SUCCESS_EXITVAL:
391b2d29daeSVladimir Kotal        logger.error('Command returned {}'.format(ret))
392b2d29daeSVladimir Kotal        logger.error(indexer.geterroutput())
393b2d29daeSVladimir Kotal        raise Exception("Failed to create bare configuration")
394b2d29daeSVladimir Kotal
395b2d29daeSVladimir Kotal
396f1835fddSVladimir Kotaldef get_num_from_env(logger, env_name, default_value):
397f1835fddSVladimir Kotal    value = default_value
398f1835fddSVladimir Kotal    env_str = os.environ.get(env_name)
399f1835fddSVladimir Kotal    if env_str:
400f1835fddSVladimir Kotal        try:
401f1835fddSVladimir Kotal            n = int(env_str)
402f1835fddSVladimir Kotal            if n >= 0:
403f1835fddSVladimir Kotal                value = n
404f1835fddSVladimir Kotal        except ValueError:
405f1835fddSVladimir Kotal            logger.error("{} is not a number: {}".
406f1835fddSVladimir Kotal                         format(env_name, env_str))
407f1835fddSVladimir Kotal
408f1835fddSVladimir Kotal    return value
409f1835fddSVladimir Kotal
410f1835fddSVladimir Kotal
411f750ae73SVladimir Kotaldef check_index_and_wipe_out(logger):
412f750ae73SVladimir Kotal    """
413f750ae73SVladimir Kotal    Check index by running the indexer. If the index does not match
414f750ae73SVladimir Kotal    currently running version and the CHECK_INDEX environment variable
415f750ae73SVladimir Kotal    is non empty, wipe out the directories under data root.
416f750ae73SVladimir Kotal    """
417f750ae73SVladimir Kotal    check_index = os.environ.get('CHECK_INDEX')
418f750ae73SVladimir Kotal    if check_index and os.path.exists(OPENGROK_CONFIG_FILE):
419f750ae73SVladimir Kotal        logger.info('Checking if index matches current version')
420f750ae73SVladimir Kotal        indexer_options = ['-R', OPENGROK_CONFIG_FILE, '--checkIndex']
421f750ae73SVladimir Kotal        indexer = Indexer(indexer_options, logger=logger,
422f750ae73SVladimir Kotal                          jar=OPENGROK_JAR, doprint=True)
423f750ae73SVladimir Kotal        indexer.execute()
424f750ae73SVladimir Kotal        if indexer.getretcode() == 1:
425f750ae73SVladimir Kotal            logger.info('Wiping out data root')
426f750ae73SVladimir Kotal            root = OPENGROK_DATA_ROOT
427f750ae73SVladimir Kotal            for entry in os.listdir(root):
428f750ae73SVladimir Kotal                path = os.path.join(root, entry)
429f750ae73SVladimir Kotal                if os.path.isdir(path):
430f750ae73SVladimir Kotal                    try:
431f750ae73SVladimir Kotal                        logger.info("Removing '{}'".format(path))
432f750ae73SVladimir Kotal                        shutil.rmtree(path)
433f750ae73SVladimir Kotal                    except Exception as e:
434f750ae73SVladimir Kotal                        logger.error("cannot delete '{}': {}".format(path, e))
435f750ae73SVladimir Kotal
436f750ae73SVladimir Kotal
437d4cbb62dSVladimir Kotaldef start_rest_thread(logger):
438d4cbb62dSVladimir Kotal    rest_port = get_num_from_env(logger, 'REST_PORT', 5000)
439d4cbb62dSVladimir Kotal    token = os.environ.get('REST_TOKEN')
440d4cbb62dSVladimir Kotal    global expected_token
441d4cbb62dSVladimir Kotal    if token:
442d4cbb62dSVladimir Kotal        logger.debug("Setting expected token for REST endpoint"
443d4cbb62dSVladimir Kotal                     "on port {}".format(rest_port))
444d4cbb62dSVladimir Kotal        expected_token = token
445d4cbb62dSVladimir Kotal    logger.debug("Starting REST thread to listen for requests "
446d4cbb62dSVladimir Kotal                 "on port {} on the {} endpoint".
447d4cbb62dSVladimir Kotal                 format(rest_port, REINDEX_POINT))
448d4cbb62dSVladimir Kotal    rest_thread = threading.Thread(target=rest_function,
449d4cbb62dSVladimir Kotal                                   name="REST thread",
450d4cbb62dSVladimir Kotal                                   args=(logger, rest_port), daemon=True)
451d4cbb62dSVladimir Kotal    rest_thread.start()
452d4cbb62dSVladimir Kotal
453d4cbb62dSVladimir Kotal
454d4cbb62dSVladimir Kotaldef start_timeout_thread(logger, sync_period):
45586d11813SVladimir Kotal    logger.debug("Starting timeout thread")
456d4cbb62dSVladimir Kotal    thread = threading.Thread(target=timeout_loop,
457d4cbb62dSVladimir Kotal                              name="Timeout thread",
458d4cbb62dSVladimir Kotal                              args=(logger, sync_period), daemon=True)
459d4cbb62dSVladimir Kotal    thread.start()
460d4cbb62dSVladimir Kotal
461d4cbb62dSVladimir Kotal
462f7d6d77cSVladimir Kotaldef main():
463b2d29daeSVladimir Kotal    log_level = os.environ.get('OPENGROK_LOG_LEVEL')
464b2d29daeSVladimir Kotal    if log_level:
465b2d29daeSVladimir Kotal        log_level = get_log_level(log_level)
466b2d29daeSVladimir Kotal    else:
467b2d29daeSVladimir Kotal        log_level = logging.INFO
468b2d29daeSVladimir Kotal
469b2d29daeSVladimir Kotal    logger = get_console_logger(get_class_basename(), log_level)
470b2d29daeSVladimir Kotal
47185ee3da3SVladimir Kotal    try:
47285ee3da3SVladimir Kotal        with open(os.path.join(OPENGROK_BASE_DIR, "VERSION"), "r") as f:
47385ee3da3SVladimir Kotal            version = f.read()
47485ee3da3SVladimir Kotal            logger.info("Running version {}".format(version))
47585ee3da3SVladimir Kotal    except Exception:
47685ee3da3SVladimir Kotal        pass
47785ee3da3SVladimir Kotal
478f7d6d77cSVladimir Kotal    uri, url_root = set_url_root(logger, os.environ.get('URL_ROOT'))
479f7d6d77cSVladimir Kotal    logger.debug("URL_ROOT = {}".format(url_root))
480f7d6d77cSVladimir Kotal    logger.debug("URI = {}".format(uri))
481b2d29daeSVladimir Kotal
48254289af4SVladimir Kotal    sync_period = get_num_from_env(logger, 'SYNC_PERIOD_MINUTES', 10)
4833d5852a4SVladimir Kotal    if sync_period == 0:
484b531e965SVladimir Kotal        logger.info("periodic synchronization disabled")
485b2d29daeSVladimir Kotal    else:
4863d5852a4SVladimir Kotal        logger.info("synchronization period = {} minutes".format(sync_period))
487b2d29daeSVladimir Kotal
488b2d29daeSVladimir Kotal    # Note that deploy is done before Tomcat is started.
489f7d6d77cSVladimir Kotal    deploy(logger, url_root)
490b2d29daeSVladimir Kotal
491f7d6d77cSVladimir Kotal    if url_root != '/source':
492f7d6d77cSVladimir Kotal        setup_redirect_source(logger, url_root)
493b2d29daeSVladimir Kotal
494b2d29daeSVladimir Kotal    env = {}
495dadacd3dSAdam Hornacek    extra_indexer_options = os.environ.get('INDEXER_OPT', '')
4964810eb96SVladimir Kotal    if extra_indexer_options:
4974810eb96SVladimir Kotal        logger.info("extra indexer options: {}".format(extra_indexer_options))
4984810eb96SVladimir Kotal        env['OPENGROK_INDEXER_OPTIONAL_ARGS'] = extra_indexer_options
4995036d900SVladimir Kotal    if os.environ.get(NOMIRROR_ENV_NAME):
500*cec5eb3aSVladimir Kotal        env[OPENGROK_NO_MIRROR_ENV] = os.environ.get(NOMIRROR_ENV_NAME)
501b2d29daeSVladimir Kotal    logger.debug('Extra environment: {}'.format(env))
502b2d29daeSVladimir Kotal
5034810eb96SVladimir Kotal    use_projects = True
5044810eb96SVladimir Kotal    if os.environ.get('AVOID_PROJECTS'):
5054810eb96SVladimir Kotal        use_projects = False
5064810eb96SVladimir Kotal
507b2d29daeSVladimir Kotal    #
508b2d29daeSVladimir Kotal    # Create empty configuration to avoid the non existent file exception
509b2d29daeSVladimir Kotal    # in the web app during the first web app startup.
510b2d29daeSVladimir Kotal    #
511b2d29daeSVladimir Kotal    if not os.path.exists(OPENGROK_CONFIG_FILE) or \
512b2d29daeSVladimir Kotal            os.path.getsize(OPENGROK_CONFIG_FILE) == 0:
51319f6e235SVladimir Kotal        create_bare_config(logger, use_projects, extra_indexer_options.split())
514b2d29daeSVladimir Kotal
515b9361335SVladimir Kotal    #
516f750ae73SVladimir Kotal    # Index check needs read-only configuration so it is placed
517f750ae73SVladimir Kotal    # right after create_bare_config().
518f750ae73SVladimir Kotal    #
519f750ae73SVladimir Kotal    check_index_and_wipe_out(logger)
520f750ae73SVladimir Kotal
521f750ae73SVladimir Kotal    #
522b9361335SVladimir Kotal    # If there is read-only configuration file, merge it with current
523b9361335SVladimir Kotal    # configuration.
524b9361335SVladimir Kotal    #
525b9361335SVladimir Kotal    read_only_config_file = os.environ.get('READONLY_CONFIG_FILE')
526b9361335SVladimir Kotal    if read_only_config_file and os.path.exists(read_only_config_file):
527b9361335SVladimir Kotal        logger.info('Merging read-only configuration from \'{}\' with current '
528b9361335SVladimir Kotal                    'configuration in \'{}\''.format(read_only_config_file,
529b9361335SVladimir Kotal                                                     OPENGROK_CONFIG_FILE))
530b9361335SVladimir Kotal        out_file = None
531b9361335SVladimir Kotal        with tempfile.NamedTemporaryFile(mode='w+', delete=False,
532b9361335SVladimir Kotal                                         prefix='merged_config') as tmp_out:
53370e4aafbSVladimir Kotal            out_file = tmp_out.name
534b9361335SVladimir Kotal            merge_config_files(read_only_config_file, OPENGROK_CONFIG_FILE,
535b9361335SVladimir Kotal                               tmp_out, jar=OPENGROK_JAR, loglevel=log_level)
536b9361335SVladimir Kotal
537b9361335SVladimir Kotal        if out_file and os.path.getsize(out_file) > 0:
538b9361335SVladimir Kotal            shutil.move(tmp_out.name, OPENGROK_CONFIG_FILE)
539b9361335SVladimir Kotal        else:
540b9361335SVladimir Kotal            logger.warning('Failed to merge read-only configuration, '
541b9361335SVladimir Kotal                           'leaving the original in place')
54270e4aafbSVladimir Kotal            if out_file:
54370e4aafbSVladimir Kotal                os.remove(out_file)
544b9361335SVladimir Kotal
5455036d900SVladimir Kotal    sync_enabled = True
5464810eb96SVladimir Kotal    if use_projects:
547f1835fddSVladimir Kotal        num_workers = get_num_from_env(logger, 'WORKERS',
548f1835fddSVladimir Kotal                                       multiprocessing.cpu_count())
549b2d29daeSVladimir Kotal        logger.info('Number of sync workers: {}'.format(num_workers))
550b2d29daeSVladimir Kotal
5515036d900SVladimir Kotal        if not os.environ.get(NOMIRROR_ENV_NAME):
552941332aaSVladimir Kotal            mirror_config = os.path.join(OPENGROK_CONFIG_DIR, "mirror.yml")
553941332aaSVladimir Kotal            if not os.path.exists(mirror_config):
554941332aaSVladimir Kotal                with open(mirror_config, 'w') as fp:
555fa3facf6SVladimir Kotal                    fp.write("# Empty config file for opengrok-mirror\n")
5565036d900SVladimir Kotal            else:
5575036d900SVladimir Kotal                conf = read_config(logger, mirror_config)
5585036d900SVladimir Kotal                logger.info("Checking mirror configuration in '{}'".
5595036d900SVladimir Kotal                            format(mirror_config))
5605036d900SVladimir Kotal                if not check_configuration(conf):
5615036d900SVladimir Kotal                    logger.error("Mirror configuration in '{}' is invalid, "
5625036d900SVladimir Kotal                                 "disabling sync".format(mirror_config))
5635036d900SVladimir Kotal                    sync_enabled = False
564941332aaSVladimir Kotal
5654810eb96SVladimir Kotal        worker_function = project_syncer
5664810eb96SVladimir Kotal        syncer_args = (logger, log_level, uri,
567f7d6d77cSVladimir Kotal                       OPENGROK_CONFIG_FILE,
568d4cbb62dSVladimir Kotal                       num_workers, env)
5694810eb96SVladimir Kotal    else:
5704810eb96SVladimir Kotal        worker_function = indexer_no_projects
571d4cbb62dSVladimir Kotal        syncer_args = (logger, uri, OPENGROK_CONFIG_FILE,
5724810eb96SVladimir Kotal                       extra_indexer_options)
5734810eb96SVladimir Kotal
5745036d900SVladimir Kotal    if sync_enabled:
5754810eb96SVladimir Kotal        logger.debug("Starting sync thread")
5765036d900SVladimir Kotal        sync_thread = threading.Thread(target=worker_function,
5775036d900SVladimir Kotal                                       name="Sync thread",
5786100daaaSVladimir Kotal                                       args=syncer_args, daemon=True)
5796100daaaSVladimir Kotal        sync_thread.start()
580b2d29daeSVladimir Kotal
581d4cbb62dSVladimir Kotal        start_rest_thread(logger)
58286d11813SVladimir Kotal        if sync_period > 0:
583d4cbb62dSVladimir Kotal            start_timeout_thread(logger, sync_period)
584f1835fddSVladimir Kotal
585b2d29daeSVladimir Kotal    # Start Tomcat last. It will be the foreground process.
586b2d29daeSVladimir Kotal    logger.info("Starting Tomcat")
5876100daaaSVladimir Kotal    global tomcat_popen
5886100daaaSVladimir Kotal    tomcat_popen = subprocess.Popen([os.path.join(tomcat_root, 'bin',
5896100daaaSVladimir Kotal                                                  'catalina.sh'),
5906100daaaSVladimir Kotal                                    'run'])
5916100daaaSVladimir Kotal    tomcat_popen.wait()
592f7d6d77cSVladimir Kotal
593f7d6d77cSVladimir Kotal
594e6daae6cSVladimir Kotaldef signal_handler(signum, frame):
59523cd7cffSVladimir Kotal    print("Received signal {}".format(signum))
59623cd7cffSVladimir Kotal
5976100daaaSVladimir Kotal    global tomcat_popen
5986100daaaSVladimir Kotal    print("Terminating Tomcat {}".format(tomcat_popen))
5996100daaaSVladimir Kotal    tomcat_popen.terminate()
6006100daaaSVladimir Kotal
6019e27dd5fSVladimir Kotal    sys.exit(0)
602e6daae6cSVladimir Kotal
603e6daae6cSVladimir Kotal
604e6daae6cSVladimir Kotalif __name__ == "__main__":
605e6daae6cSVladimir Kotal    signal.signal(signal.SIGTERM, signal_handler)
606e6daae6cSVladimir Kotal    signal.signal(signal.SIGINT, signal_handler)
607e6daae6cSVladimir Kotal
608e6daae6cSVladimir Kotal    main()
609