xref: /OpenGrok/docker/start.py (revision 4990cf90abdf9f502100708d80386ae497a9e630)
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')
77*4990cf90SVladimir KotalREINDEX_POINT = '/reindex'
78f1835fddSVladimir Kotal
79f1835fddSVladimir Kotal
80f1835fddSVladimir Kotal@auth.verify_token
81f1835fddSVladimir Kotaldef verify_token(token):
82f1835fddSVladimir Kotal    if expected_token is None:
83f1835fddSVladimir Kotal        return "yes"
84f1835fddSVladimir Kotal
85f1835fddSVladimir Kotal    if token is not None and token == expected_token:
86f1835fddSVladimir Kotal        return "yes"
87f1835fddSVladimir Kotal
88f1835fddSVladimir Kotal
89*4990cf90SVladimir Kotal@app.route(REINDEX_POINT)
90f1835fddSVladimir Kotal@auth.login_required
91f1835fddSVladimir Kotaldef index():
92f1835fddSVladimir Kotal    # Signal the sync/indexer thread.
93f1835fddSVladimir Kotal    sleep_event.set()
94f1835fddSVladimir Kotal    sleep_event.clear()
95f1835fddSVladimir Kotal
96f1835fddSVladimir Kotal    return "Reindex triggered"
97f1835fddSVladimir Kotal
98f1835fddSVladimir Kotal
99f1835fddSVladimir Kotaldef rest_function(logger, rest_port):
100f1835fddSVladimir Kotal    logger.info("Starting REST app on port {}".format(rest_port))
101f1835fddSVladimir Kotal    serve(app, host="0.0.0.0", port=rest_port)
102f1835fddSVladimir Kotal
103b2d29daeSVladimir Kotal
104f7d6d77cSVladimir Kotaldef set_url_root(logger, url_root):
105b2d29daeSVladimir Kotal    """
106b2d29daeSVladimir Kotal    Set URL root and URI based on input
107788f14d3SVladimir Kotal    :param logger: logger instance
108b2d29daeSVladimir Kotal    :param url_root: input
109b2d29daeSVladimir Kotal    :return: URI and URL root
110b2d29daeSVladimir Kotal    """
111b2d29daeSVladimir Kotal    if not url_root:
112b2d29daeSVladimir Kotal        url_root = '/'
113b2d29daeSVladimir Kotal
114b2d29daeSVladimir Kotal    if ' ' in url_root:
115b2d29daeSVladimir Kotal        logger.warn('Deployment path contains spaces. Deploying to root')
116b2d29daeSVladimir Kotal        url_root = '/'
117b2d29daeSVladimir Kotal
118b2d29daeSVladimir Kotal    # Remove leading and trailing slashes
119b2d29daeSVladimir Kotal    if url_root.startswith('/'):
120b2d29daeSVladimir Kotal        url_root = url_root[1:]
121b2d29daeSVladimir Kotal    if url_root.endswith('/'):
122b2d29daeSVladimir Kotal        url_root = url_root[:-1]
123b2d29daeSVladimir Kotal
124b2d29daeSVladimir Kotal    uri = "http://localhost:8080/" + url_root
125b2d29daeSVladimir Kotal    #
126b2d29daeSVladimir Kotal    # Make sure URI ends with slash. This is important for the various API
127b2d29daeSVladimir Kotal    # calls, notably for those that check the HTTP error code.
128b2d29daeSVladimir Kotal    # Normally accessing the URI without the terminating slash results in
129b2d29daeSVladimir Kotal    # HTTP redirect (code 302) instead of success (200).
130b2d29daeSVladimir Kotal    #
131b2d29daeSVladimir Kotal    if not uri.endswith('/'):
132b2d29daeSVladimir Kotal        uri = uri + '/'
133b2d29daeSVladimir Kotal
134b2d29daeSVladimir Kotal    return uri, url_root
135b2d29daeSVladimir Kotal
136b2d29daeSVladimir Kotal
137b2d29daeSVladimir Kotaldef get_war_name(url_root):
138d44cbea0SVladimir Kotal    """
139d44cbea0SVladimir Kotal    :param url_root: web app URL root
140d44cbea0SVladimir Kotal    :return: filename of the WAR file
141d44cbea0SVladimir Kotal    """
142b2d29daeSVladimir Kotal    if len(url_root) == 0:
143b2d29daeSVladimir Kotal        return "ROOT.war"
144f7d6d77cSVladimir Kotal
145b2d29daeSVladimir Kotal    return url_root + ".war"
146b2d29daeSVladimir Kotal
147b2d29daeSVladimir Kotal
148f7d6d77cSVladimir Kotaldef deploy(logger, url_root):
149b2d29daeSVladimir Kotal    """
150b2d29daeSVladimir Kotal    Deploy the web application
151788f14d3SVladimir Kotal    :param logger: logger instance
152b2d29daeSVladimir Kotal    :param url_root: web app URL root
153b2d29daeSVladimir Kotal    """
154b2d29daeSVladimir Kotal
155b2d29daeSVladimir Kotal    logger.info('Deploying web application')
156b2d29daeSVladimir Kotal    webapps_dir = os.path.join(tomcat_root, 'webapps')
157b2d29daeSVladimir Kotal    if not os.path.isdir(webapps_dir):
158b2d29daeSVladimir Kotal        raise Exception("{} is not a directory".format(webapps_dir))
159b2d29daeSVladimir Kotal
160b2d29daeSVladimir Kotal    for item in os.listdir(webapps_dir):
161b2d29daeSVladimir Kotal        subdir = os.path.join(webapps_dir, item)
162b2d29daeSVladimir Kotal        if os.path.isdir(subdir):
163b2d29daeSVladimir Kotal            logger.debug("Removing '{}' directory recursively".format(subdir))
164b2d29daeSVladimir Kotal            shutil.rmtree(subdir)
165b2d29daeSVladimir Kotal
166b2d29daeSVladimir Kotal    deploy_war(logger, os.path.join(OPENGROK_LIB_DIR, "source.war"),
167b2d29daeSVladimir Kotal               os.path.join(OPENGROK_WEBAPPS_DIR, get_war_name(url_root)),
168b2d29daeSVladimir Kotal               OPENGROK_CONFIG_FILE, None)
169b2d29daeSVladimir Kotal
170b2d29daeSVladimir Kotal
171f7d6d77cSVladimir Kotaldef setup_redirect_source(logger, url_root):
172b2d29daeSVladimir Kotal    """
173b2d29daeSVladimir Kotal    Set up redirect from /source
174b2d29daeSVladimir Kotal    """
175b2d29daeSVladimir Kotal    logger.debug("Setting up redirect from /source to '{}'".format(url_root))
176b2d29daeSVladimir Kotal    source_dir = os.path.join(OPENGROK_WEBAPPS_DIR, "source")
177b2d29daeSVladimir Kotal    if not os.path.isdir(source_dir):
178b2d29daeSVladimir Kotal        os.makedirs(source_dir)
179b2d29daeSVladimir Kotal
180b2d29daeSVladimir Kotal    with open(os.path.join(source_dir, "index.jsp"), "w+") as index:
181b2d29daeSVladimir Kotal        index.write("<% response.sendRedirect(\"/{}\"); %>".format(url_root))
182b2d29daeSVladimir Kotal
183b2d29daeSVladimir Kotal
184f7d6d77cSVladimir Kotaldef wait_for_tomcat(logger, uri):
185b2d29daeSVladimir Kotal    """
186b2d29daeSVladimir Kotal    Active/busy waiting for Tomcat to come up.
187b2d29daeSVladimir Kotal    Currently there is no upper time bound.
188b2d29daeSVladimir Kotal    """
189b2d29daeSVladimir Kotal    logger.info("Waiting for Tomcat to start")
190b2d29daeSVladimir Kotal
191b2d29daeSVladimir Kotal    while True:
192b2d29daeSVladimir Kotal        try:
193d44cbea0SVladimir Kotal            ret = get(uri)
194d44cbea0SVladimir Kotal            status = ret.status_code
195b2d29daeSVladimir Kotal        except ConnectionError:
196b2d29daeSVladimir Kotal            status = 0
197b2d29daeSVladimir Kotal
198b2d29daeSVladimir Kotal        if status != 200:
199b2d29daeSVladimir Kotal            logger.debug("Got status {} for {}, sleeping for 1 second".
200b2d29daeSVladimir Kotal                         format(status, uri))
201b2d29daeSVladimir Kotal            time.sleep(1)
202b2d29daeSVladimir Kotal        else:
203b2d29daeSVladimir Kotal            break
204b2d29daeSVladimir Kotal
205b2d29daeSVladimir Kotal    logger.info("Tomcat is ready")
206b2d29daeSVladimir Kotal
207b2d29daeSVladimir Kotal
208f7d6d77cSVladimir Kotaldef refresh_projects(logger, uri):
209b2d29daeSVladimir Kotal    """
210b2d29daeSVladimir Kotal    Ensure each immediate source root subdirectory is a project.
211b2d29daeSVladimir Kotal    """
212b2d29daeSVladimir Kotal    webapp_projects = list_projects(logger, uri)
2136100daaaSVladimir Kotal    if not webapp_projects:
2146100daaaSVladimir Kotal        return
2156100daaaSVladimir Kotal
216b2d29daeSVladimir Kotal    logger.debug('Projects from the web app: {}'.format(webapp_projects))
217b2d29daeSVladimir Kotal    src_root = OPENGROK_SRC_ROOT
218b2d29daeSVladimir Kotal
219b2d29daeSVladimir Kotal    # Add projects.
220b2d29daeSVladimir Kotal    for item in os.listdir(src_root):
221b2d29daeSVladimir Kotal        logger.debug('Got item {}'.format(item))
222b2d29daeSVladimir Kotal        if os.path.isdir(os.path.join(src_root, item)):
223b2d29daeSVladimir Kotal            if item not in webapp_projects:
224b2d29daeSVladimir Kotal                logger.info("Adding project {}".format(item))
225b2d29daeSVladimir Kotal                add_project(logger, item, uri)
226b2d29daeSVladimir Kotal
227b2d29daeSVladimir Kotal    # Remove projects
228b2d29daeSVladimir Kotal    for item in webapp_projects:
229b2d29daeSVladimir Kotal        if not os.path.isdir(os.path.join(src_root, item)):
230b2d29daeSVladimir Kotal            logger.info("Deleting project {}".format(item))
231b2d29daeSVladimir Kotal            delete_project(logger, item, uri)
232b2d29daeSVladimir Kotal
233b2d29daeSVladimir Kotal
234f7d6d77cSVladimir Kotaldef save_config(logger, uri, config_path):
235b2d29daeSVladimir Kotal    """
236b2d29daeSVladimir Kotal    Retrieve configuration from the web app and write it to file.
237788f14d3SVladimir Kotal    :param logger: logger instance
238b2d29daeSVladimir Kotal    :param uri: web app URI
239b2d29daeSVladimir Kotal    :param config_path: file path
240b2d29daeSVladimir Kotal    """
241b2d29daeSVladimir Kotal
242b2d29daeSVladimir Kotal    logger.info('Saving configuration to {}'.format(config_path))
243b2d29daeSVladimir Kotal    config = get_configuration(logger, uri)
244b2d29daeSVladimir Kotal    with open(config_path, "w+") as config_file:
245b2d29daeSVladimir Kotal        config_file.write(config)
246b2d29daeSVladimir Kotal
247b2d29daeSVladimir Kotal
248b2d29daeSVladimir Kotaldef merge_commands_env(commands, env):
249b2d29daeSVladimir Kotal    """
250b2d29daeSVladimir Kotal    Merge environment into command structure. If any of the commands has
251b2d29daeSVladimir Kotal    an environment already set, the env is merged in.
252b2d29daeSVladimir Kotal    :param commands: commands structure
253b2d29daeSVladimir Kotal    :param env: environment dictionary
254b2d29daeSVladimir Kotal    :return: updated commands structure
255b2d29daeSVladimir Kotal    """
256b2d29daeSVladimir Kotal    for cmd in commands:
257b2d29daeSVladimir Kotal        cmd_env = cmd.get('env')
258b2d29daeSVladimir Kotal        if cmd_env:
259b2d29daeSVladimir Kotal            cmd.env.update(env)
260b2d29daeSVladimir Kotal        else:
261b2d29daeSVladimir Kotal            cmd['env'] = env
262b2d29daeSVladimir Kotal
263b2d29daeSVladimir Kotal    return commands
264b2d29daeSVladimir Kotal
265b2d29daeSVladimir Kotal
2664810eb96SVladimir Kotaldef indexer_no_projects(logger, uri, config_path, sync_period,
2674810eb96SVladimir Kotal                        extra_indexer_options):
2684810eb96SVladimir Kotal    """
2694810eb96SVladimir Kotal    Project less indexer
2704810eb96SVladimir Kotal    """
2714810eb96SVladimir Kotal
2724810eb96SVladimir Kotal    wait_for_tomcat(logger, uri)
2734810eb96SVladimir Kotal
274f1835fddSVladimir Kotal    periodic_sync = True
275f1835fddSVladimir Kotal    if sync_period is None or sync_period == 0:
276f1835fddSVladimir Kotal        periodic_sync = False
277f1835fddSVladimir Kotal
2784810eb96SVladimir Kotal    while True:
2794810eb96SVladimir Kotal        indexer_options = ['-s', OPENGROK_SRC_ROOT,
2804810eb96SVladimir Kotal                           '-d', OPENGROK_DATA_ROOT,
2814810eb96SVladimir Kotal                           '-c', '/usr/local/bin/ctags',
2824810eb96SVladimir Kotal                           '--remote', 'on',
2834810eb96SVladimir Kotal                           '-H',
2844810eb96SVladimir Kotal                           '-W', config_path,
2854810eb96SVladimir Kotal                           '-U', uri]
2864810eb96SVladimir Kotal        if extra_indexer_options:
2874810eb96SVladimir Kotal            logger.debug("Adding extra indexer options: {}".
2884810eb96SVladimir Kotal                         format(extra_indexer_options))
2894810eb96SVladimir Kotal            indexer_options.extend(extra_indexer_options.split())
2904810eb96SVladimir Kotal        indexer = Indexer(indexer_options, logger=logger,
291d2f093daSVladimir Kotal                          jar=OPENGROK_JAR, doprint=True)
2924810eb96SVladimir Kotal        indexer.execute()
2934810eb96SVladimir Kotal
294f1835fddSVladimir Kotal        if periodic_sync:
2954810eb96SVladimir Kotal            sleep_seconds = sync_period * 60
2964810eb96SVladimir Kotal            logger.info("Sleeping for {} seconds".format(sleep_seconds))
2974810eb96SVladimir Kotal            time.sleep(sleep_seconds)
298b531e965SVladimir Kotal        elif not periodic_sync:
299*4990cf90SVladimir Kotal            logger.info("Waiting for reindex trigger on {} endpoint".
300*4990cf90SVladimir Kotal                        format(REINDEX_POINT))
301b531e965SVladimir Kotal            sleep_event.wait()
3024810eb96SVladimir Kotal
3034810eb96SVladimir Kotal
3044810eb96SVladimir Kotaldef project_syncer(logger, loglevel, uri, config_path, sync_period,
3054810eb96SVladimir Kotal                   numworkers, env):
306b2d29daeSVladimir Kotal    """
307b2d29daeSVladimir Kotal    Wrapper for running opengrok-sync.
308b2d29daeSVladimir Kotal    To be run in a thread/process in the background.
309b2d29daeSVladimir Kotal    """
310b2d29daeSVladimir Kotal
311f7d6d77cSVladimir Kotal    wait_for_tomcat(logger, uri)
312b2d29daeSVladimir Kotal
313f1835fddSVladimir Kotal    periodic_sync = True
314f1835fddSVladimir Kotal    if sync_period is None or sync_period == 0:
315f1835fddSVladimir Kotal        periodic_sync = False
316f1835fddSVladimir Kotal
317b2d29daeSVladimir Kotal    while True:
318f7d6d77cSVladimir Kotal        refresh_projects(logger, uri)
319b2d29daeSVladimir Kotal
320b2d29daeSVladimir Kotal        if os.environ.get('OPENGROK_SYNC_YML'):  # debug only
321b2d29daeSVladimir Kotal            config_file = os.environ.get('OPENGROK_SYNC_YML')
322b2d29daeSVladimir Kotal        else:
323b2d29daeSVladimir Kotal            config_file = os.path.join(fs_root, 'scripts', 'sync.yml')
324b2d29daeSVladimir Kotal        config = read_config(logger, config_file)
325b2d29daeSVladimir Kotal        if config is None:
326b2d29daeSVladimir Kotal            logger.error("Cannot read config file from {}".format(config_file))
327b2d29daeSVladimir Kotal            raise Exception("no sync config")
328b2d29daeSVladimir Kotal
329b2d29daeSVladimir Kotal        projects = list_projects(logger, uri)
3306100daaaSVladimir Kotal        if projects:
331b2d29daeSVladimir Kotal            #
332b2d29daeSVladimir Kotal            # The driveon=True is needed for the initial indexing of newly
3336100daaaSVladimir Kotal            # added project, otherwise the incoming check in the
3346100daaaSVladimir Kotal            # opengrok-mirror program would short circuit it.
335b2d29daeSVladimir Kotal            #
336b2d29daeSVladimir Kotal            if env:
337b2d29daeSVladimir Kotal                logger.info('Merging commands with environment')
338b2d29daeSVladimir Kotal                commands = merge_commands_env(config["commands"], env)
339b2d29daeSVladimir Kotal                logger.debug(config['commands'])
340b2d29daeSVladimir Kotal            else:
341b2d29daeSVladimir Kotal                commands = config["commands"]
342b2d29daeSVladimir Kotal
343b2d29daeSVladimir Kotal            logger.info("Sync starting")
344b2d29daeSVladimir Kotal            do_sync(loglevel, commands, config.get('cleanup'),
345b2d29daeSVladimir Kotal                    projects, config.get("ignore_errors"), uri,
346b2d29daeSVladimir Kotal                    numworkers, driveon=True, logger=logger, print_output=True)
347b2d29daeSVladimir Kotal            logger.info("Sync done")
348b2d29daeSVladimir Kotal
349b2d29daeSVladimir Kotal            # Workaround for https://github.com/oracle/opengrok/issues/1670
350b2d29daeSVladimir Kotal            Path(os.path.join(OPENGROK_DATA_ROOT, 'timestamp')).touch()
351b2d29daeSVladimir Kotal
352f7d6d77cSVladimir Kotal            save_config(logger, uri, config_path)
353b2d29daeSVladimir Kotal
354f1835fddSVladimir Kotal        if periodic_sync:
3553d5852a4SVladimir Kotal            sleep_seconds = sync_period * 60
356b2d29daeSVladimir Kotal            logger.info("Sleeping for {} seconds".format(sleep_seconds))
357b2d29daeSVladimir Kotal            time.sleep(sleep_seconds)
358b531e965SVladimir Kotal        elif not periodic_sync:
359*4990cf90SVladimir Kotal            logger.info("Waiting for reindex trigger on {} endpoint".
360*4990cf90SVladimir Kotal                        format(REINDEX_POINT))
361b531e965SVladimir Kotal            sleep_event.wait()
362b2d29daeSVladimir Kotal
363b2d29daeSVladimir Kotal
36419f6e235SVladimir Kotaldef create_bare_config(logger, use_projects, extra_indexer_options=None):
365b2d29daeSVladimir Kotal    """
366b2d29daeSVladimir Kotal    Create bare configuration file with a few basic settings.
367b2d29daeSVladimir Kotal    """
368b2d29daeSVladimir Kotal
369b2d29daeSVladimir Kotal    logger.info('Creating bare configuration in {}'.
370b2d29daeSVladimir Kotal                format(OPENGROK_CONFIG_FILE))
3714810eb96SVladimir Kotal    indexer_options = ['-s', OPENGROK_SRC_ROOT,
372b2d29daeSVladimir Kotal                       '-d', OPENGROK_DATA_ROOT,
373b2d29daeSVladimir Kotal                       '-c', '/usr/local/bin/ctags',
374b2d29daeSVladimir Kotal                       '--remote', 'on',
3754810eb96SVladimir Kotal                       '-H',
37619f6e235SVladimir Kotal                       '-S',
377b2d29daeSVladimir Kotal                       '-W', OPENGROK_CONFIG_FILE,
3784810eb96SVladimir Kotal                       '--noIndex']
3794810eb96SVladimir Kotal
3809d6d7c28SVladimir Kotal    if extra_indexer_options:
3815b279385SVladimir Kotal        if type(extra_indexer_options) is not list:
3825b279385SVladimir Kotal            raise Exception("extra_indexer_options has to be a list")
3839d6d7c28SVladimir Kotal        indexer_options.extend(extra_indexer_options)
38419f6e235SVladimir Kotal    if use_projects:
38519f6e235SVladimir Kotal        indexer_options.append('-P')
3864810eb96SVladimir Kotal    indexer = Indexer(indexer_options,
3874810eb96SVladimir Kotal                      jar=OPENGROK_JAR,
388b2d29daeSVladimir Kotal                      logger=logger, doprint=True)
389b2d29daeSVladimir Kotal    indexer.execute()
390b2d29daeSVladimir Kotal    ret = indexer.getretcode()
391b2d29daeSVladimir Kotal    if ret != SUCCESS_EXITVAL:
392b2d29daeSVladimir Kotal        logger.error('Command returned {}'.format(ret))
393b2d29daeSVladimir Kotal        logger.error(indexer.geterroutput())
394b2d29daeSVladimir Kotal        raise Exception("Failed to create bare configuration")
395b2d29daeSVladimir Kotal
396b2d29daeSVladimir Kotal
397f1835fddSVladimir Kotaldef get_num_from_env(logger, env_name, default_value):
398f1835fddSVladimir Kotal    value = default_value
399f1835fddSVladimir Kotal    env_str = os.environ.get(env_name)
400f1835fddSVladimir Kotal    if env_str:
401f1835fddSVladimir Kotal        try:
402f1835fddSVladimir Kotal            n = int(env_str)
403f1835fddSVladimir Kotal            if n >= 0:
404f1835fddSVladimir Kotal                value = n
405f1835fddSVladimir Kotal        except ValueError:
406f1835fddSVladimir Kotal            logger.error("{} is not a number: {}".
407f1835fddSVladimir Kotal                         format(env_name, env_str))
408f1835fddSVladimir Kotal
409f1835fddSVladimir Kotal    return value
410f1835fddSVladimir Kotal
411f1835fddSVladimir Kotal
412f750ae73SVladimir Kotaldef check_index_and_wipe_out(logger):
413f750ae73SVladimir Kotal    """
414f750ae73SVladimir Kotal    Check index by running the indexer. If the index does not match
415f750ae73SVladimir Kotal    currently running version and the CHECK_INDEX environment variable
416f750ae73SVladimir Kotal    is non empty, wipe out the directories under data root.
417f750ae73SVladimir Kotal    """
418f750ae73SVladimir Kotal    check_index = os.environ.get('CHECK_INDEX')
419f750ae73SVladimir Kotal    if check_index and os.path.exists(OPENGROK_CONFIG_FILE):
420f750ae73SVladimir Kotal        logger.info('Checking if index matches current version')
421f750ae73SVladimir Kotal        indexer_options = ['-R', OPENGROK_CONFIG_FILE, '--checkIndex']
422f750ae73SVladimir Kotal        indexer = Indexer(indexer_options, logger=logger,
423f750ae73SVladimir Kotal                          jar=OPENGROK_JAR, doprint=True)
424f750ae73SVladimir Kotal        indexer.execute()
425f750ae73SVladimir Kotal        if indexer.getretcode() == 1:
426f750ae73SVladimir Kotal            logger.info('Wiping out data root')
427f750ae73SVladimir Kotal            root = OPENGROK_DATA_ROOT
428f750ae73SVladimir Kotal            for entry in os.listdir(root):
429f750ae73SVladimir Kotal                path = os.path.join(root, entry)
430f750ae73SVladimir Kotal                if os.path.isdir(path):
431f750ae73SVladimir Kotal                    try:
432f750ae73SVladimir Kotal                        logger.info("Removing '{}'".format(path))
433f750ae73SVladimir Kotal                        shutil.rmtree(path)
434f750ae73SVladimir Kotal                    except Exception as e:
435f750ae73SVladimir Kotal                        logger.error("cannot delete '{}': {}".format(path, e))
436f750ae73SVladimir Kotal
437f750ae73SVladimir Kotal
438f7d6d77cSVladimir Kotaldef main():
439b2d29daeSVladimir Kotal    log_level = os.environ.get('OPENGROK_LOG_LEVEL')
440b2d29daeSVladimir Kotal    if log_level:
441b2d29daeSVladimir Kotal        log_level = get_log_level(log_level)
442b2d29daeSVladimir Kotal    else:
443b2d29daeSVladimir Kotal        log_level = logging.INFO
444b2d29daeSVladimir Kotal
445b2d29daeSVladimir Kotal    logger = get_console_logger(get_class_basename(), log_level)
446b2d29daeSVladimir Kotal
447f7d6d77cSVladimir Kotal    uri, url_root = set_url_root(logger, os.environ.get('URL_ROOT'))
448f7d6d77cSVladimir Kotal    logger.debug("URL_ROOT = {}".format(url_root))
449f7d6d77cSVladimir Kotal    logger.debug("URI = {}".format(uri))
450b2d29daeSVladimir Kotal
45154289af4SVladimir Kotal    sync_period = get_num_from_env(logger, 'SYNC_PERIOD_MINUTES', 10)
4523d5852a4SVladimir Kotal    if sync_period == 0:
453b531e965SVladimir Kotal        logger.info("periodic synchronization disabled")
454b2d29daeSVladimir Kotal    else:
4553d5852a4SVladimir Kotal        logger.info("synchronization period = {} minutes".format(sync_period))
456b2d29daeSVladimir Kotal
457b2d29daeSVladimir Kotal    # Note that deploy is done before Tomcat is started.
458f7d6d77cSVladimir Kotal    deploy(logger, url_root)
459b2d29daeSVladimir Kotal
460f7d6d77cSVladimir Kotal    if url_root != '/source':
461f7d6d77cSVladimir Kotal        setup_redirect_source(logger, url_root)
462b2d29daeSVladimir Kotal
463b2d29daeSVladimir Kotal    env = {}
464dadacd3dSAdam Hornacek    extra_indexer_options = os.environ.get('INDEXER_OPT', '')
4654810eb96SVladimir Kotal    if extra_indexer_options:
4664810eb96SVladimir Kotal        logger.info("extra indexer options: {}".format(extra_indexer_options))
4674810eb96SVladimir Kotal        env['OPENGROK_INDEXER_OPTIONAL_ARGS'] = extra_indexer_options
468b2d29daeSVladimir Kotal    if os.environ.get('NOMIRROR'):
469b2d29daeSVladimir Kotal        env['OPENGROK_NO_MIRROR'] = os.environ.get('NOMIRROR')
470b2d29daeSVladimir Kotal    logger.debug('Extra environment: {}'.format(env))
471b2d29daeSVladimir Kotal
4724810eb96SVladimir Kotal    use_projects = True
4734810eb96SVladimir Kotal    if os.environ.get('AVOID_PROJECTS'):
4744810eb96SVladimir Kotal        use_projects = False
4754810eb96SVladimir Kotal
476b2d29daeSVladimir Kotal    #
477b2d29daeSVladimir Kotal    # Create empty configuration to avoid the non existent file exception
478b2d29daeSVladimir Kotal    # in the web app during the first web app startup.
479b2d29daeSVladimir Kotal    #
480b2d29daeSVladimir Kotal    if not os.path.exists(OPENGROK_CONFIG_FILE) or \
481b2d29daeSVladimir Kotal            os.path.getsize(OPENGROK_CONFIG_FILE) == 0:
48219f6e235SVladimir Kotal        create_bare_config(logger, use_projects, extra_indexer_options.split())
483b2d29daeSVladimir Kotal
484b9361335SVladimir Kotal    #
485f750ae73SVladimir Kotal    # Index check needs read-only configuration so it is placed
486f750ae73SVladimir Kotal    # right after create_bare_config().
487f750ae73SVladimir Kotal    #
488f750ae73SVladimir Kotal    check_index_and_wipe_out(logger)
489f750ae73SVladimir Kotal
490f750ae73SVladimir Kotal    #
491b9361335SVladimir Kotal    # If there is read-only configuration file, merge it with current
492b9361335SVladimir Kotal    # configuration.
493b9361335SVladimir Kotal    #
494b9361335SVladimir Kotal    read_only_config_file = os.environ.get('READONLY_CONFIG_FILE')
495b9361335SVladimir Kotal    if read_only_config_file and os.path.exists(read_only_config_file):
496b9361335SVladimir Kotal        logger.info('Merging read-only configuration from \'{}\' with current '
497b9361335SVladimir Kotal                    'configuration in \'{}\''.format(read_only_config_file,
498b9361335SVladimir Kotal                                                     OPENGROK_CONFIG_FILE))
499b9361335SVladimir Kotal        out_file = None
500b9361335SVladimir Kotal        with tempfile.NamedTemporaryFile(mode='w+', delete=False,
501b9361335SVladimir Kotal                                         prefix='merged_config') as tmp_out:
50270e4aafbSVladimir Kotal            out_file = tmp_out.name
503b9361335SVladimir Kotal            merge_config_files(read_only_config_file, OPENGROK_CONFIG_FILE,
504b9361335SVladimir Kotal                               tmp_out, jar=OPENGROK_JAR, loglevel=log_level)
505b9361335SVladimir Kotal
506b9361335SVladimir Kotal        if out_file and os.path.getsize(out_file) > 0:
507b9361335SVladimir Kotal            shutil.move(tmp_out.name, OPENGROK_CONFIG_FILE)
508b9361335SVladimir Kotal        else:
509b9361335SVladimir Kotal            logger.warning('Failed to merge read-only configuration, '
510b9361335SVladimir Kotal                           'leaving the original in place')
51170e4aafbSVladimir Kotal            if out_file:
51270e4aafbSVladimir Kotal                os.remove(out_file)
513b9361335SVladimir Kotal
5144810eb96SVladimir Kotal    if use_projects:
515f1835fddSVladimir Kotal        num_workers = get_num_from_env(logger, 'WORKERS',
516f1835fddSVladimir Kotal                                       multiprocessing.cpu_count())
517b2d29daeSVladimir Kotal        logger.info('Number of sync workers: {}'.format(num_workers))
518b2d29daeSVladimir Kotal
5194810eb96SVladimir Kotal        worker_function = project_syncer
5204810eb96SVladimir Kotal        syncer_args = (logger, log_level, uri,
521f7d6d77cSVladimir Kotal                       OPENGROK_CONFIG_FILE,
5224810eb96SVladimir Kotal                       sync_period, num_workers, env)
5234810eb96SVladimir Kotal    else:
5244810eb96SVladimir Kotal        worker_function = indexer_no_projects
5254810eb96SVladimir Kotal        syncer_args = (logger, uri, OPENGROK_CONFIG_FILE, sync_period,
5264810eb96SVladimir Kotal                       extra_indexer_options)
5274810eb96SVladimir Kotal
5284810eb96SVladimir Kotal    logger.debug("Starting sync thread")
5296100daaaSVladimir Kotal    sync_thread = threading.Thread(target=worker_function, name="Sync thread",
5306100daaaSVladimir Kotal                                   args=syncer_args, daemon=True)
5316100daaaSVladimir Kotal    sync_thread.start()
532b2d29daeSVladimir Kotal
533f1835fddSVladimir Kotal    rest_port = get_num_from_env(logger, 'REST_PORT', 5000)
534f1835fddSVladimir Kotal    token = os.environ.get('REST_TOKEN')
535f1835fddSVladimir Kotal    global expected_token
536f1835fddSVladimir Kotal    if token:
537f1835fddSVladimir Kotal        logger.debug("Setting expected token for REST endpoint"
538f1835fddSVladimir Kotal                     "on port {} to '{}'".format(rest_port, token))
539f1835fddSVladimir Kotal        expected_token = token
540f1835fddSVladimir Kotal    logger.debug("Starting REST thread")
5416100daaaSVladimir Kotal    rest_thread = threading.Thread(target=rest_function, name="REST thread",
5426100daaaSVladimir Kotal                                   args=(logger, rest_port), daemon=True)
5436100daaaSVladimir Kotal    rest_thread.start()
544f1835fddSVladimir Kotal
545b2d29daeSVladimir Kotal    # Start Tomcat last. It will be the foreground process.
546b2d29daeSVladimir Kotal    logger.info("Starting Tomcat")
5476100daaaSVladimir Kotal    global tomcat_popen
5486100daaaSVladimir Kotal    tomcat_popen = subprocess.Popen([os.path.join(tomcat_root, 'bin',
5496100daaaSVladimir Kotal                                                  'catalina.sh'),
5506100daaaSVladimir Kotal                                    'run'])
5516100daaaSVladimir Kotal    tomcat_popen.wait()
552f7d6d77cSVladimir Kotal
553f7d6d77cSVladimir Kotal
554e6daae6cSVladimir Kotaldef signal_handler(signum, frame):
5556100daaaSVladimir Kotal    global tomcat_popen
5566100daaaSVladimir Kotal    print("Terminating Tomcat {}".format(tomcat_popen))
5576100daaaSVladimir Kotal    tomcat_popen.terminate()
5586100daaaSVladimir Kotal
5596100daaaSVladimir Kotal    sys.exit(1)
560e6daae6cSVladimir Kotal
561e6daae6cSVladimir Kotal
562e6daae6cSVladimir Kotalif __name__ == "__main__":
563e6daae6cSVladimir Kotal    signal.signal(signal.SIGTERM, signal_handler)
564e6daae6cSVladimir Kotal    signal.signal(signal.SIGINT, signal_handler)
565e6daae6cSVladimir Kotal
566e6daae6cSVladimir Kotal    main()
567