xref: /OpenGrok/docker/start.py (revision b9361335ec266d2bd84d6bb0293ddc1661802636)
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
29*b9361335SVladimir Kotalimport tempfile
30b2d29daeSVladimir Kotalimport threading
31b2d29daeSVladimir Kotalimport time
32d44cbea0SVladimir Kotalfrom pathlib import Path
33d44cbea0SVladimir Kotalfrom requests import get, ConnectionError
34f1835fddSVladimir Kotalfrom flask import Flask
35f1835fddSVladimir Kotalfrom flask_httpauth import HTTPTokenAuth
36f1835fddSVladimir Kotalfrom waitress import serve
37b2d29daeSVladimir Kotal
38b2d29daeSVladimir Kotalfrom opengrok_tools.utils.log import get_console_logger, \
39b2d29daeSVladimir Kotal    get_log_level, get_class_basename
40b2d29daeSVladimir Kotalfrom opengrok_tools.deploy import deploy_war
41b2d29daeSVladimir Kotalfrom opengrok_tools.utils.indexer import Indexer
42b2d29daeSVladimir Kotalfrom opengrok_tools.sync import do_sync
43*b9361335SVladimir Kotalfrom opengrok_tools.config_merge import merge_config_files
44b2d29daeSVladimir Kotalfrom opengrok_tools.utils.opengrok import list_projects, \
451822b591SVladimir Kotal    add_project, delete_project, get_configuration, set_config_value
46b2d29daeSVladimir Kotalfrom opengrok_tools.utils.readconfig import read_config
47b2d29daeSVladimir Kotalfrom opengrok_tools.utils.exitvals import SUCCESS_EXITVAL
48b2d29daeSVladimir Kotal
49b2d29daeSVladimir Kotal
50b2d29daeSVladimir Kotalfs_root = os.path.abspath('.').split(os.path.sep)[0] + os.path.sep
51b2d29daeSVladimir Kotalif os.environ.get('OPENGROK_TOMCAT_ROOT'):  # debug only
52b2d29daeSVladimir Kotal    tomcat_root = os.environ.get('OPENGROK_TOMCAT_ROOT')
53b2d29daeSVladimir Kotalelse:
54b2d29daeSVladimir Kotal    tomcat_root = os.path.join(fs_root, "usr", "local", "tomcat")
55b2d29daeSVladimir Kotal
56b2d29daeSVladimir Kotalif os.environ.get('OPENGROK_ROOT'):  # debug only
57b2d29daeSVladimir Kotal    OPENGROK_BASE_DIR = os.environ.get('OPENGROK_ROOT')
58b2d29daeSVladimir Kotalelse:
59b2d29daeSVladimir Kotal    OPENGROK_BASE_DIR = os.path.join(fs_root, "opengrok")
60b2d29daeSVladimir Kotal
61b2d29daeSVladimir KotalOPENGROK_LIB_DIR = os.path.join(OPENGROK_BASE_DIR, "lib")
62b2d29daeSVladimir KotalOPENGROK_DATA_ROOT = os.path.join(OPENGROK_BASE_DIR, "data")
63b2d29daeSVladimir KotalOPENGROK_SRC_ROOT = os.path.join(OPENGROK_BASE_DIR, "src")
64b2d29daeSVladimir KotalBODY_INCLUDE_FILE = os.path.join(OPENGROK_DATA_ROOT, "body_include")
65b2d29daeSVladimir KotalOPENGROK_CONFIG_FILE = os.path.join(OPENGROK_BASE_DIR, "etc",
66b2d29daeSVladimir Kotal                                    "configuration.xml")
67b2d29daeSVladimir KotalOPENGROK_WEBAPPS_DIR = os.path.join(tomcat_root, "webapps")
684810eb96SVladimir KotalOPENGROK_JAR = os.path.join(OPENGROK_LIB_DIR, 'opengrok.jar')
69b2d29daeSVladimir Kotal
70f1835fddSVladimir Kotalexpected_token = None
71f1835fddSVladimir Kotal
72f1835fddSVladimir Kotalsleep_event = threading.Event()
73f1835fddSVladimir Kotalapp = Flask(__name__)
74f1835fddSVladimir Kotalauth = HTTPTokenAuth(scheme='Bearer')
75f1835fddSVladimir Kotal
76f1835fddSVladimir Kotal
77f1835fddSVladimir Kotal@auth.verify_token
78f1835fddSVladimir Kotaldef verify_token(token):
79f1835fddSVladimir Kotal    if expected_token is None:
80f1835fddSVladimir Kotal        return "yes"
81f1835fddSVladimir Kotal
82f1835fddSVladimir Kotal    if token is not None and token == expected_token:
83f1835fddSVladimir Kotal        return "yes"
84f1835fddSVladimir Kotal
85f1835fddSVladimir Kotal
86f1835fddSVladimir Kotal@app.route('/reindex')
87f1835fddSVladimir Kotal@auth.login_required
88f1835fddSVladimir Kotaldef index():
89f1835fddSVladimir Kotal    # Signal the sync/indexer thread.
90f1835fddSVladimir Kotal    sleep_event.set()
91f1835fddSVladimir Kotal    sleep_event.clear()
92f1835fddSVladimir Kotal
93f1835fddSVladimir Kotal    return "Reindex triggered"
94f1835fddSVladimir Kotal
95f1835fddSVladimir Kotal
96f1835fddSVladimir Kotaldef rest_function(logger, rest_port):
97f1835fddSVladimir Kotal    logger.info("Starting REST app on port {}".format(rest_port))
98f1835fddSVladimir Kotal    serve(app, host="0.0.0.0", port=rest_port)
99f1835fddSVladimir Kotal
100b2d29daeSVladimir Kotal
101f7d6d77cSVladimir Kotaldef set_url_root(logger, url_root):
102b2d29daeSVladimir Kotal    """
103b2d29daeSVladimir Kotal    Set URL root and URI based on input
104788f14d3SVladimir Kotal    :param logger: logger instance
105b2d29daeSVladimir Kotal    :param url_root: input
106b2d29daeSVladimir Kotal    :return: URI and URL root
107b2d29daeSVladimir Kotal    """
108b2d29daeSVladimir Kotal    if not url_root:
109b2d29daeSVladimir Kotal        url_root = '/'
110b2d29daeSVladimir Kotal
111b2d29daeSVladimir Kotal    if ' ' in url_root:
112b2d29daeSVladimir Kotal        logger.warn('Deployment path contains spaces. Deploying to root')
113b2d29daeSVladimir Kotal        url_root = '/'
114b2d29daeSVladimir Kotal
115b2d29daeSVladimir Kotal    # Remove leading and trailing slashes
116b2d29daeSVladimir Kotal    if url_root.startswith('/'):
117b2d29daeSVladimir Kotal        url_root = url_root[1:]
118b2d29daeSVladimir Kotal    if url_root.endswith('/'):
119b2d29daeSVladimir Kotal        url_root = url_root[:-1]
120b2d29daeSVladimir Kotal
121b2d29daeSVladimir Kotal    uri = "http://localhost:8080/" + url_root
122b2d29daeSVladimir Kotal    #
123b2d29daeSVladimir Kotal    # Make sure URI ends with slash. This is important for the various API
124b2d29daeSVladimir Kotal    # calls, notably for those that check the HTTP error code.
125b2d29daeSVladimir Kotal    # Normally accessing the URI without the terminating slash results in
126b2d29daeSVladimir Kotal    # HTTP redirect (code 302) instead of success (200).
127b2d29daeSVladimir Kotal    #
128b2d29daeSVladimir Kotal    if not uri.endswith('/'):
129b2d29daeSVladimir Kotal        uri = uri + '/'
130b2d29daeSVladimir Kotal
131b2d29daeSVladimir Kotal    return uri, url_root
132b2d29daeSVladimir Kotal
133b2d29daeSVladimir Kotal
134b2d29daeSVladimir Kotaldef get_war_name(url_root):
135d44cbea0SVladimir Kotal    """
136d44cbea0SVladimir Kotal    :param url_root: web app URL root
137d44cbea0SVladimir Kotal    :return: filename of the WAR file
138d44cbea0SVladimir Kotal    """
139b2d29daeSVladimir Kotal    if len(url_root) == 0:
140b2d29daeSVladimir Kotal        return "ROOT.war"
141f7d6d77cSVladimir Kotal
142b2d29daeSVladimir Kotal    return url_root + ".war"
143b2d29daeSVladimir Kotal
144b2d29daeSVladimir Kotal
145f7d6d77cSVladimir Kotaldef deploy(logger, url_root):
146b2d29daeSVladimir Kotal    """
147b2d29daeSVladimir Kotal    Deploy the web application
148788f14d3SVladimir Kotal    :param logger: logger instance
149b2d29daeSVladimir Kotal    :param url_root: web app URL root
150b2d29daeSVladimir Kotal    """
151b2d29daeSVladimir Kotal
152b2d29daeSVladimir Kotal    logger.info('Deploying web application')
153b2d29daeSVladimir Kotal    webapps_dir = os.path.join(tomcat_root, 'webapps')
154b2d29daeSVladimir Kotal    if not os.path.isdir(webapps_dir):
155b2d29daeSVladimir Kotal        raise Exception("{} is not a directory".format(webapps_dir))
156b2d29daeSVladimir Kotal
157b2d29daeSVladimir Kotal    for item in os.listdir(webapps_dir):
158b2d29daeSVladimir Kotal        subdir = os.path.join(webapps_dir, item)
159b2d29daeSVladimir Kotal        if os.path.isdir(subdir):
160b2d29daeSVladimir Kotal            logger.debug("Removing '{}' directory recursively".format(subdir))
161b2d29daeSVladimir Kotal            shutil.rmtree(subdir)
162b2d29daeSVladimir Kotal
163b2d29daeSVladimir Kotal    deploy_war(logger, os.path.join(OPENGROK_LIB_DIR, "source.war"),
164b2d29daeSVladimir Kotal               os.path.join(OPENGROK_WEBAPPS_DIR, get_war_name(url_root)),
165b2d29daeSVladimir Kotal               OPENGROK_CONFIG_FILE, None)
166b2d29daeSVladimir Kotal
167b2d29daeSVladimir Kotal
168f7d6d77cSVladimir Kotaldef setup_redirect_source(logger, url_root):
169b2d29daeSVladimir Kotal    """
170b2d29daeSVladimir Kotal    Set up redirect from /source
171b2d29daeSVladimir Kotal    """
172b2d29daeSVladimir Kotal    logger.debug("Setting up redirect from /source to '{}'".format(url_root))
173b2d29daeSVladimir Kotal    source_dir = os.path.join(OPENGROK_WEBAPPS_DIR, "source")
174b2d29daeSVladimir Kotal    if not os.path.isdir(source_dir):
175b2d29daeSVladimir Kotal        os.makedirs(source_dir)
176b2d29daeSVladimir Kotal
177b2d29daeSVladimir Kotal    with open(os.path.join(source_dir, "index.jsp"), "w+") as index:
178b2d29daeSVladimir Kotal        index.write("<% response.sendRedirect(\"/{}\"); %>".format(url_root))
179b2d29daeSVladimir Kotal
180b2d29daeSVladimir Kotal
181f7d6d77cSVladimir Kotaldef wait_for_tomcat(logger, uri):
182b2d29daeSVladimir Kotal    """
183b2d29daeSVladimir Kotal    Active/busy waiting for Tomcat to come up.
184b2d29daeSVladimir Kotal    Currently there is no upper time bound.
185b2d29daeSVladimir Kotal    """
186b2d29daeSVladimir Kotal    logger.info("Waiting for Tomcat to start")
187b2d29daeSVladimir Kotal
188b2d29daeSVladimir Kotal    while True:
189b2d29daeSVladimir Kotal        try:
190d44cbea0SVladimir Kotal            ret = get(uri)
191d44cbea0SVladimir Kotal            status = ret.status_code
192b2d29daeSVladimir Kotal        except ConnectionError:
193b2d29daeSVladimir Kotal            status = 0
194b2d29daeSVladimir Kotal
195b2d29daeSVladimir Kotal        if status != 200:
196b2d29daeSVladimir Kotal            logger.debug("Got status {} for {}, sleeping for 1 second".
197b2d29daeSVladimir Kotal                         format(status, uri))
198b2d29daeSVladimir Kotal            time.sleep(1)
199b2d29daeSVladimir Kotal        else:
200b2d29daeSVladimir Kotal            break
201b2d29daeSVladimir Kotal
202b2d29daeSVladimir Kotal    logger.info("Tomcat is ready")
203b2d29daeSVladimir Kotal
204b2d29daeSVladimir Kotal
205f7d6d77cSVladimir Kotaldef refresh_projects(logger, uri):
206b2d29daeSVladimir Kotal    """
207b2d29daeSVladimir Kotal    Ensure each immediate source root subdirectory is a project.
208b2d29daeSVladimir Kotal    """
209b2d29daeSVladimir Kotal    webapp_projects = list_projects(logger, uri)
210b2d29daeSVladimir Kotal    logger.debug('Projects from the web app: {}'.format(webapp_projects))
211b2d29daeSVladimir Kotal    src_root = OPENGROK_SRC_ROOT
212b2d29daeSVladimir Kotal
213b2d29daeSVladimir Kotal    # Add projects.
214b2d29daeSVladimir Kotal    for item in os.listdir(src_root):
215b2d29daeSVladimir Kotal        logger.debug('Got item {}'.format(item))
216b2d29daeSVladimir Kotal        if os.path.isdir(os.path.join(src_root, item)):
217b2d29daeSVladimir Kotal            if item not in webapp_projects:
218b2d29daeSVladimir Kotal                logger.info("Adding project {}".format(item))
219b2d29daeSVladimir Kotal                add_project(logger, item, uri)
220b2d29daeSVladimir Kotal
221b2d29daeSVladimir Kotal    # Remove projects
222b2d29daeSVladimir Kotal    for item in webapp_projects:
223b2d29daeSVladimir Kotal        if not os.path.isdir(os.path.join(src_root, item)):
224b2d29daeSVladimir Kotal            logger.info("Deleting project {}".format(item))
225b2d29daeSVladimir Kotal            delete_project(logger, item, uri)
226b2d29daeSVladimir Kotal
227b2d29daeSVladimir Kotal
228f7d6d77cSVladimir Kotaldef save_config(logger, uri, config_path):
229b2d29daeSVladimir Kotal    """
230b2d29daeSVladimir Kotal    Retrieve configuration from the web app and write it to file.
231788f14d3SVladimir Kotal    :param logger: logger instance
232b2d29daeSVladimir Kotal    :param uri: web app URI
233b2d29daeSVladimir Kotal    :param config_path: file path
234b2d29daeSVladimir Kotal    """
235b2d29daeSVladimir Kotal
236b2d29daeSVladimir Kotal    logger.info('Saving configuration to {}'.format(config_path))
237b2d29daeSVladimir Kotal    config = get_configuration(logger, uri)
238b2d29daeSVladimir Kotal    with open(config_path, "w+") as config_file:
239b2d29daeSVladimir Kotal        config_file.write(config)
240b2d29daeSVladimir Kotal
241b2d29daeSVladimir Kotal
242b2d29daeSVladimir Kotaldef merge_commands_env(commands, env):
243b2d29daeSVladimir Kotal    """
244b2d29daeSVladimir Kotal    Merge environment into command structure. If any of the commands has
245b2d29daeSVladimir Kotal    an environment already set, the env is merged in.
246b2d29daeSVladimir Kotal    :param commands: commands structure
247b2d29daeSVladimir Kotal    :param env: environment dictionary
248b2d29daeSVladimir Kotal    :return: updated commands structure
249b2d29daeSVladimir Kotal    """
250b2d29daeSVladimir Kotal    for cmd in commands:
251b2d29daeSVladimir Kotal        cmd_env = cmd.get('env')
252b2d29daeSVladimir Kotal        if cmd_env:
253b2d29daeSVladimir Kotal            cmd.env.update(env)
254b2d29daeSVladimir Kotal        else:
255b2d29daeSVladimir Kotal            cmd['env'] = env
256b2d29daeSVladimir Kotal
257b2d29daeSVladimir Kotal    return commands
258b2d29daeSVladimir Kotal
259b2d29daeSVladimir Kotal
2604810eb96SVladimir Kotaldef indexer_no_projects(logger, uri, config_path, sync_period,
2614810eb96SVladimir Kotal                        extra_indexer_options):
2624810eb96SVladimir Kotal    """
2634810eb96SVladimir Kotal    Project less indexer
2644810eb96SVladimir Kotal    """
2654810eb96SVladimir Kotal
2664810eb96SVladimir Kotal    wait_for_tomcat(logger, uri)
2674810eb96SVladimir Kotal
268f1835fddSVladimir Kotal    periodic_sync = True
269f1835fddSVladimir Kotal    if sync_period is None or sync_period == 0:
270f1835fddSVladimir Kotal        periodic_sync = False
271f1835fddSVladimir Kotal
2724810eb96SVladimir Kotal    while True:
273f1835fddSVladimir Kotal        if not periodic_sync:
274f1835fddSVladimir Kotal            sleep_event.wait()
275f1835fddSVladimir Kotal
2764810eb96SVladimir Kotal        indexer_options = ['-s', OPENGROK_SRC_ROOT,
2774810eb96SVladimir Kotal                           '-d', OPENGROK_DATA_ROOT,
2784810eb96SVladimir Kotal                           '-c', '/usr/local/bin/ctags',
2794810eb96SVladimir Kotal                           '--remote', 'on',
2804810eb96SVladimir Kotal                           '-H',
2814810eb96SVladimir Kotal                           '-W', config_path,
2824810eb96SVladimir Kotal                           '-U', uri]
2834810eb96SVladimir Kotal        if extra_indexer_options:
2844810eb96SVladimir Kotal            logger.debug("Adding extra indexer options: {}".
2854810eb96SVladimir Kotal                         format(extra_indexer_options))
2864810eb96SVladimir Kotal            indexer_options.extend(extra_indexer_options.split())
2874810eb96SVladimir Kotal        indexer = Indexer(indexer_options, logger=logger,
288d2f093daSVladimir Kotal                          jar=OPENGROK_JAR, doprint=True)
2894810eb96SVladimir Kotal        indexer.execute()
2904810eb96SVladimir Kotal
291f1835fddSVladimir Kotal        if periodic_sync:
2924810eb96SVladimir Kotal            sleep_seconds = sync_period * 60
2934810eb96SVladimir Kotal            logger.info("Sleeping for {} seconds".format(sleep_seconds))
2944810eb96SVladimir Kotal            time.sleep(sleep_seconds)
2954810eb96SVladimir Kotal
2964810eb96SVladimir Kotal
2974810eb96SVladimir Kotaldef project_syncer(logger, loglevel, uri, config_path, sync_period,
2984810eb96SVladimir Kotal                   numworkers, env):
299b2d29daeSVladimir Kotal    """
300b2d29daeSVladimir Kotal    Wrapper for running opengrok-sync.
301b2d29daeSVladimir Kotal    To be run in a thread/process in the background.
302b2d29daeSVladimir Kotal    """
303b2d29daeSVladimir Kotal
304f7d6d77cSVladimir Kotal    wait_for_tomcat(logger, uri)
305b2d29daeSVladimir Kotal
3061822b591SVladimir Kotal    set_config_value(logger, 'projectsEnabled', 'true', uri)
3071822b591SVladimir Kotal
308f1835fddSVladimir Kotal    periodic_sync = True
309f1835fddSVladimir Kotal    if sync_period is None or sync_period == 0:
310f1835fddSVladimir Kotal        periodic_sync = False
311f1835fddSVladimir Kotal
312b2d29daeSVladimir Kotal    while True:
313f1835fddSVladimir Kotal        if not periodic_sync:
314f1835fddSVladimir Kotal            sleep_event.wait()
315f1835fddSVladimir Kotal
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)
328b2d29daeSVladimir Kotal        #
329b2d29daeSVladimir Kotal        # The driveon=True is needed for the initial indexing of newly
330b2d29daeSVladimir Kotal        # added project, otherwise the incoming check in the opengrok-mirror
3313d5852a4SVladimir Kotal        # 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
351f1835fddSVladimir Kotal        if periodic_sync:
3523d5852a4SVladimir Kotal            sleep_seconds = sync_period * 60
353b2d29daeSVladimir Kotal            logger.info("Sleeping for {} seconds".format(sleep_seconds))
354b2d29daeSVladimir Kotal            time.sleep(sleep_seconds)
355b2d29daeSVladimir Kotal
356b2d29daeSVladimir Kotal
3579d6d7c28SVladimir Kotaldef create_bare_config(logger, extra_indexer_options=None):
358b2d29daeSVladimir Kotal    """
359b2d29daeSVladimir Kotal    Create bare configuration file with a few basic settings.
360b2d29daeSVladimir Kotal    """
361b2d29daeSVladimir Kotal
362b2d29daeSVladimir Kotal    logger.info('Creating bare configuration in {}'.
363b2d29daeSVladimir Kotal                format(OPENGROK_CONFIG_FILE))
3644810eb96SVladimir Kotal    indexer_options = ['-s', OPENGROK_SRC_ROOT,
365b2d29daeSVladimir Kotal                       '-d', OPENGROK_DATA_ROOT,
366b2d29daeSVladimir Kotal                       '-c', '/usr/local/bin/ctags',
367b2d29daeSVladimir Kotal                       '--remote', 'on',
3684810eb96SVladimir Kotal                       '-H',
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)
3764810eb96SVladimir Kotal    indexer = Indexer(indexer_options,
3774810eb96SVladimir Kotal                      jar=OPENGROK_JAR,
378b2d29daeSVladimir Kotal                      logger=logger, doprint=True)
379b2d29daeSVladimir Kotal    indexer.execute()
380b2d29daeSVladimir Kotal    ret = indexer.getretcode()
381b2d29daeSVladimir Kotal    if ret != SUCCESS_EXITVAL:
382b2d29daeSVladimir Kotal        logger.error('Command returned {}'.format(ret))
383b2d29daeSVladimir Kotal        logger.error(indexer.geterroutput())
384b2d29daeSVladimir Kotal        raise Exception("Failed to create bare configuration")
385b2d29daeSVladimir Kotal
386b2d29daeSVladimir Kotal
387f1835fddSVladimir Kotaldef get_num_from_env(logger, env_name, default_value):
388f1835fddSVladimir Kotal    value = default_value
389f1835fddSVladimir Kotal    env_str = os.environ.get(env_name)
390f1835fddSVladimir Kotal    if env_str:
391f1835fddSVladimir Kotal        try:
392f1835fddSVladimir Kotal            n = int(env_str)
393f1835fddSVladimir Kotal            if n >= 0:
394f1835fddSVladimir Kotal                value = n
395f1835fddSVladimir Kotal        except ValueError:
396f1835fddSVladimir Kotal            logger.error("{} is not a number: {}".
397f1835fddSVladimir Kotal                         format(env_name, env_str))
398f1835fddSVladimir Kotal
399f1835fddSVladimir Kotal    return value
400f1835fddSVladimir Kotal
401f1835fddSVladimir Kotal
402f7d6d77cSVladimir Kotaldef main():
403b2d29daeSVladimir Kotal    log_level = os.environ.get('OPENGROK_LOG_LEVEL')
404b2d29daeSVladimir Kotal    if log_level:
405b2d29daeSVladimir Kotal        log_level = get_log_level(log_level)
406b2d29daeSVladimir Kotal    else:
407b2d29daeSVladimir Kotal        log_level = logging.INFO
408b2d29daeSVladimir Kotal
409b2d29daeSVladimir Kotal    logger = get_console_logger(get_class_basename(), log_level)
410b2d29daeSVladimir Kotal
411f7d6d77cSVladimir Kotal    uri, url_root = set_url_root(logger, os.environ.get('URL_ROOT'))
412f7d6d77cSVladimir Kotal    logger.debug("URL_ROOT = {}".format(url_root))
413f7d6d77cSVladimir Kotal    logger.debug("URI = {}".format(uri))
414b2d29daeSVladimir Kotal
4153d5852a4SVladimir Kotal    # default period for syncing (in minutes)
416f1835fddSVladimir Kotal    sync_period = get_num_from_env(logger, 'SYNC_TIME_MINUTES', 10)
4173d5852a4SVladimir Kotal    if sync_period == 0:
4183d5852a4SVladimir Kotal        logger.info("synchronization disabled")
419b2d29daeSVladimir Kotal    else:
4203d5852a4SVladimir Kotal        logger.info("synchronization period = {} minutes".format(sync_period))
421b2d29daeSVladimir Kotal
422b2d29daeSVladimir Kotal    # Note that deploy is done before Tomcat is started.
423f7d6d77cSVladimir Kotal    deploy(logger, url_root)
424b2d29daeSVladimir Kotal
425f7d6d77cSVladimir Kotal    if url_root != '/source':
426f7d6d77cSVladimir Kotal        setup_redirect_source(logger, url_root)
427b2d29daeSVladimir Kotal
428b2d29daeSVladimir Kotal    env = {}
429dadacd3dSAdam Hornacek    extra_indexer_options = os.environ.get('INDEXER_OPT', '')
4304810eb96SVladimir Kotal    if extra_indexer_options:
4314810eb96SVladimir Kotal        logger.info("extra indexer options: {}".format(extra_indexer_options))
4324810eb96SVladimir Kotal        env['OPENGROK_INDEXER_OPTIONAL_ARGS'] = extra_indexer_options
433b2d29daeSVladimir Kotal    if os.environ.get('NOMIRROR'):
434b2d29daeSVladimir Kotal        env['OPENGROK_NO_MIRROR'] = os.environ.get('NOMIRROR')
435b2d29daeSVladimir Kotal    logger.debug('Extra environment: {}'.format(env))
436b2d29daeSVladimir Kotal
4374810eb96SVladimir Kotal    use_projects = True
4384810eb96SVladimir Kotal    if os.environ.get('AVOID_PROJECTS'):
4394810eb96SVladimir Kotal        use_projects = False
4404810eb96SVladimir Kotal
441b2d29daeSVladimir Kotal    #
442b2d29daeSVladimir Kotal    # Create empty configuration to avoid the non existent file exception
443b2d29daeSVladimir Kotal    # in the web app during the first web app startup.
444b2d29daeSVladimir Kotal    #
445b2d29daeSVladimir Kotal    if not os.path.exists(OPENGROK_CONFIG_FILE) or \
446b2d29daeSVladimir Kotal            os.path.getsize(OPENGROK_CONFIG_FILE) == 0:
447c6f6c3d1SVladimir Kotal        create_bare_config(logger, extra_indexer_options.split())
448b2d29daeSVladimir Kotal
449*b9361335SVladimir Kotal    #
450*b9361335SVladimir Kotal    # If there is read-only configuration file, merge it with current
451*b9361335SVladimir Kotal    # configuration.
452*b9361335SVladimir Kotal    #
453*b9361335SVladimir Kotal    read_only_config_file = os.environ.get('READONLY_CONFIG_FILE')
454*b9361335SVladimir Kotal    if read_only_config_file and os.path.exists(read_only_config_file):
455*b9361335SVladimir Kotal        logger.info('Merging read-only configuration from \'{}\' with current '
456*b9361335SVladimir Kotal                    'configuration in \'{}\''.format(read_only_config_file,
457*b9361335SVladimir Kotal                                                     OPENGROK_CONFIG_FILE))
458*b9361335SVladimir Kotal        out_file = None
459*b9361335SVladimir Kotal        with tempfile.NamedTemporaryFile(mode='w+', delete=False,
460*b9361335SVladimir Kotal                                         prefix='merged_config') as tmp_out:
461*b9361335SVladimir Kotal            merge_config_files(read_only_config_file, OPENGROK_CONFIG_FILE,
462*b9361335SVladimir Kotal                               tmp_out, jar=OPENGROK_JAR, loglevel=log_level)
463*b9361335SVladimir Kotal            out_file = tmp_out.name
464*b9361335SVladimir Kotal
465*b9361335SVladimir Kotal        if out_file and os.path.getsize(out_file) > 0:
466*b9361335SVladimir Kotal            shutil.move(tmp_out.name, OPENGROK_CONFIG_FILE)
467*b9361335SVladimir Kotal        else:
468*b9361335SVladimir Kotal            logger.warning('Failed to merge read-only configuration, '
469*b9361335SVladimir Kotal                           'leaving the original in place')
470*b9361335SVladimir Kotal
4714810eb96SVladimir Kotal    if use_projects:
472f1835fddSVladimir Kotal        num_workers = get_num_from_env(logger, 'WORKERS',
473f1835fddSVladimir Kotal                                       multiprocessing.cpu_count())
474b2d29daeSVladimir Kotal        logger.info('Number of sync workers: {}'.format(num_workers))
475b2d29daeSVladimir Kotal
4764810eb96SVladimir Kotal        worker_function = project_syncer
4774810eb96SVladimir Kotal        syncer_args = (logger, log_level, uri,
478f7d6d77cSVladimir Kotal                       OPENGROK_CONFIG_FILE,
4794810eb96SVladimir Kotal                       sync_period, num_workers, env)
4804810eb96SVladimir Kotal    else:
4814810eb96SVladimir Kotal        worker_function = indexer_no_projects
4824810eb96SVladimir Kotal        syncer_args = (logger, uri, OPENGROK_CONFIG_FILE, sync_period,
4834810eb96SVladimir Kotal                       extra_indexer_options)
4844810eb96SVladimir Kotal
4854810eb96SVladimir Kotal    logger.debug("Starting sync thread")
4864810eb96SVladimir Kotal    thread = threading.Thread(target=worker_function, name="Sync thread",
4874810eb96SVladimir Kotal                              args=syncer_args)
488f7d6d77cSVladimir Kotal    thread.start()
489b2d29daeSVladimir Kotal
490f1835fddSVladimir Kotal    rest_port = get_num_from_env(logger, 'REST_PORT', 5000)
491f1835fddSVladimir Kotal    token = os.environ.get('REST_TOKEN')
492f1835fddSVladimir Kotal    global expected_token
493f1835fddSVladimir Kotal    if token:
494f1835fddSVladimir Kotal        logger.debug("Setting expected token for REST endpoint"
495f1835fddSVladimir Kotal                     "on port {} to '{}'".format(rest_port, token))
496f1835fddSVladimir Kotal        expected_token = token
497f1835fddSVladimir Kotal    logger.debug("Starting REST thread")
498f1835fddSVladimir Kotal    thread = threading.Thread(target=rest_function, name="REST thread",
499f1835fddSVladimir Kotal                              args=(logger, rest_port))
500f1835fddSVladimir Kotal    thread.start()
501f1835fddSVladimir Kotal
502b2d29daeSVladimir Kotal    # Start Tomcat last. It will be the foreground process.
503b2d29daeSVladimir Kotal    logger.info("Starting Tomcat")
504b2d29daeSVladimir Kotal    subprocess.run([os.path.join(tomcat_root, 'bin', 'catalina.sh'), 'run'])
505f7d6d77cSVladimir Kotal
506f7d6d77cSVladimir Kotal
507f7d6d77cSVladimir Kotalif __name__ == "__main__":
508f7d6d77cSVladimir Kotal    main()
509