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