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