xref: /OpenGrok/docker/start.py (revision f750ae734f32e8154b5d7902121b861d6e223d9b)
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
296100daaaSVladimir Kotalimport sys
30b9361335SVladimir Kotalimport tempfile
31b2d29daeSVladimir Kotalimport threading
32b2d29daeSVladimir Kotalimport time
33d44cbea0SVladimir Kotalfrom pathlib import Path
34d44cbea0SVladimir Kotalfrom requests import get, ConnectionError
35f1835fddSVladimir Kotalfrom flask import Flask
36f1835fddSVladimir Kotalfrom flask_httpauth import HTTPTokenAuth
37f1835fddSVladimir Kotalfrom waitress import serve
38b2d29daeSVladimir Kotal
39b2d29daeSVladimir Kotalfrom opengrok_tools.utils.log import get_console_logger, \
40b2d29daeSVladimir Kotal    get_log_level, get_class_basename
41b2d29daeSVladimir Kotalfrom opengrok_tools.deploy import deploy_war
42b2d29daeSVladimir Kotalfrom opengrok_tools.utils.indexer import Indexer
43b2d29daeSVladimir Kotalfrom opengrok_tools.sync import do_sync
44b9361335SVladimir Kotalfrom opengrok_tools.config_merge import merge_config_files
45b2d29daeSVladimir Kotalfrom opengrok_tools.utils.opengrok import list_projects, \
461822b591SVladimir Kotal    add_project, delete_project, get_configuration, set_config_value
47b2d29daeSVladimir Kotalfrom opengrok_tools.utils.readconfig import read_config
48b2d29daeSVladimir Kotalfrom opengrok_tools.utils.exitvals import SUCCESS_EXITVAL
49b2d29daeSVladimir Kotal
50b2d29daeSVladimir Kotal
51b2d29daeSVladimir Kotalfs_root = os.path.abspath('.').split(os.path.sep)[0] + os.path.sep
52b2d29daeSVladimir Kotalif os.environ.get('OPENGROK_TOMCAT_ROOT'):  # debug only
53b2d29daeSVladimir Kotal    tomcat_root = os.environ.get('OPENGROK_TOMCAT_ROOT')
54b2d29daeSVladimir Kotalelse:
55b2d29daeSVladimir Kotal    tomcat_root = os.path.join(fs_root, "usr", "local", "tomcat")
56b2d29daeSVladimir Kotal
57b2d29daeSVladimir Kotalif os.environ.get('OPENGROK_ROOT'):  # debug only
58b2d29daeSVladimir Kotal    OPENGROK_BASE_DIR = os.environ.get('OPENGROK_ROOT')
59b2d29daeSVladimir Kotalelse:
60b2d29daeSVladimir Kotal    OPENGROK_BASE_DIR = os.path.join(fs_root, "opengrok")
61b2d29daeSVladimir Kotal
62b2d29daeSVladimir KotalOPENGROK_LIB_DIR = os.path.join(OPENGROK_BASE_DIR, "lib")
63b2d29daeSVladimir KotalOPENGROK_DATA_ROOT = os.path.join(OPENGROK_BASE_DIR, "data")
64b2d29daeSVladimir KotalOPENGROK_SRC_ROOT = os.path.join(OPENGROK_BASE_DIR, "src")
65b2d29daeSVladimir KotalBODY_INCLUDE_FILE = os.path.join(OPENGROK_DATA_ROOT, "body_include")
66b2d29daeSVladimir KotalOPENGROK_CONFIG_FILE = os.path.join(OPENGROK_BASE_DIR, "etc",
67b2d29daeSVladimir Kotal                                    "configuration.xml")
68b2d29daeSVladimir KotalOPENGROK_WEBAPPS_DIR = os.path.join(tomcat_root, "webapps")
694810eb96SVladimir KotalOPENGROK_JAR = os.path.join(OPENGROK_LIB_DIR, 'opengrok.jar')
70b2d29daeSVladimir Kotal
71f1835fddSVladimir Kotalexpected_token = None
72f1835fddSVladimir Kotal
73f1835fddSVladimir Kotalsleep_event = threading.Event()
74f1835fddSVladimir Kotalapp = Flask(__name__)
75f1835fddSVladimir Kotalauth = HTTPTokenAuth(scheme='Bearer')
76f1835fddSVladimir Kotal
77f1835fddSVladimir Kotal
78f1835fddSVladimir Kotal@auth.verify_token
79f1835fddSVladimir Kotaldef verify_token(token):
80f1835fddSVladimir Kotal    if expected_token is None:
81f1835fddSVladimir Kotal        return "yes"
82f1835fddSVladimir Kotal
83f1835fddSVladimir Kotal    if token is not None and token == expected_token:
84f1835fddSVladimir Kotal        return "yes"
85f1835fddSVladimir Kotal
86f1835fddSVladimir Kotal
87f1835fddSVladimir Kotal@app.route('/reindex')
88f1835fddSVladimir Kotal@auth.login_required
89f1835fddSVladimir Kotaldef index():
90f1835fddSVladimir Kotal    # Signal the sync/indexer thread.
91f1835fddSVladimir Kotal    sleep_event.set()
92f1835fddSVladimir Kotal    sleep_event.clear()
93f1835fddSVladimir Kotal
94f1835fddSVladimir Kotal    return "Reindex triggered"
95f1835fddSVladimir Kotal
96f1835fddSVladimir Kotal
97f1835fddSVladimir Kotaldef rest_function(logger, rest_port):
98f1835fddSVladimir Kotal    logger.info("Starting REST app on port {}".format(rest_port))
99f1835fddSVladimir Kotal    serve(app, host="0.0.0.0", port=rest_port)
100f1835fddSVladimir Kotal
101b2d29daeSVladimir Kotal
102f7d6d77cSVladimir Kotaldef set_url_root(logger, url_root):
103b2d29daeSVladimir Kotal    """
104b2d29daeSVladimir Kotal    Set URL root and URI based on input
105788f14d3SVladimir Kotal    :param logger: logger instance
106b2d29daeSVladimir Kotal    :param url_root: input
107b2d29daeSVladimir Kotal    :return: URI and URL root
108b2d29daeSVladimir Kotal    """
109b2d29daeSVladimir Kotal    if not url_root:
110b2d29daeSVladimir Kotal        url_root = '/'
111b2d29daeSVladimir Kotal
112b2d29daeSVladimir Kotal    if ' ' in url_root:
113b2d29daeSVladimir Kotal        logger.warn('Deployment path contains spaces. Deploying to root')
114b2d29daeSVladimir Kotal        url_root = '/'
115b2d29daeSVladimir Kotal
116b2d29daeSVladimir Kotal    # Remove leading and trailing slashes
117b2d29daeSVladimir Kotal    if url_root.startswith('/'):
118b2d29daeSVladimir Kotal        url_root = url_root[1:]
119b2d29daeSVladimir Kotal    if url_root.endswith('/'):
120b2d29daeSVladimir Kotal        url_root = url_root[:-1]
121b2d29daeSVladimir Kotal
122b2d29daeSVladimir Kotal    uri = "http://localhost:8080/" + url_root
123b2d29daeSVladimir Kotal    #
124b2d29daeSVladimir Kotal    # Make sure URI ends with slash. This is important for the various API
125b2d29daeSVladimir Kotal    # calls, notably for those that check the HTTP error code.
126b2d29daeSVladimir Kotal    # Normally accessing the URI without the terminating slash results in
127b2d29daeSVladimir Kotal    # HTTP redirect (code 302) instead of success (200).
128b2d29daeSVladimir Kotal    #
129b2d29daeSVladimir Kotal    if not uri.endswith('/'):
130b2d29daeSVladimir Kotal        uri = uri + '/'
131b2d29daeSVladimir Kotal
132b2d29daeSVladimir Kotal    return uri, url_root
133b2d29daeSVladimir Kotal
134b2d29daeSVladimir Kotal
135b2d29daeSVladimir Kotaldef get_war_name(url_root):
136d44cbea0SVladimir Kotal    """
137d44cbea0SVladimir Kotal    :param url_root: web app URL root
138d44cbea0SVladimir Kotal    :return: filename of the WAR file
139d44cbea0SVladimir Kotal    """
140b2d29daeSVladimir Kotal    if len(url_root) == 0:
141b2d29daeSVladimir Kotal        return "ROOT.war"
142f7d6d77cSVladimir Kotal
143b2d29daeSVladimir Kotal    return url_root + ".war"
144b2d29daeSVladimir Kotal
145b2d29daeSVladimir Kotal
146f7d6d77cSVladimir Kotaldef deploy(logger, url_root):
147b2d29daeSVladimir Kotal    """
148b2d29daeSVladimir Kotal    Deploy the web application
149788f14d3SVladimir Kotal    :param logger: logger instance
150b2d29daeSVladimir Kotal    :param url_root: web app URL root
151b2d29daeSVladimir Kotal    """
152b2d29daeSVladimir Kotal
153b2d29daeSVladimir Kotal    logger.info('Deploying web application')
154b2d29daeSVladimir Kotal    webapps_dir = os.path.join(tomcat_root, 'webapps')
155b2d29daeSVladimir Kotal    if not os.path.isdir(webapps_dir):
156b2d29daeSVladimir Kotal        raise Exception("{} is not a directory".format(webapps_dir))
157b2d29daeSVladimir Kotal
158b2d29daeSVladimir Kotal    for item in os.listdir(webapps_dir):
159b2d29daeSVladimir Kotal        subdir = os.path.join(webapps_dir, item)
160b2d29daeSVladimir Kotal        if os.path.isdir(subdir):
161b2d29daeSVladimir Kotal            logger.debug("Removing '{}' directory recursively".format(subdir))
162b2d29daeSVladimir Kotal            shutil.rmtree(subdir)
163b2d29daeSVladimir Kotal
164b2d29daeSVladimir Kotal    deploy_war(logger, os.path.join(OPENGROK_LIB_DIR, "source.war"),
165b2d29daeSVladimir Kotal               os.path.join(OPENGROK_WEBAPPS_DIR, get_war_name(url_root)),
166b2d29daeSVladimir Kotal               OPENGROK_CONFIG_FILE, None)
167b2d29daeSVladimir Kotal
168b2d29daeSVladimir Kotal
169f7d6d77cSVladimir Kotaldef setup_redirect_source(logger, url_root):
170b2d29daeSVladimir Kotal    """
171b2d29daeSVladimir Kotal    Set up redirect from /source
172b2d29daeSVladimir Kotal    """
173b2d29daeSVladimir Kotal    logger.debug("Setting up redirect from /source to '{}'".format(url_root))
174b2d29daeSVladimir Kotal    source_dir = os.path.join(OPENGROK_WEBAPPS_DIR, "source")
175b2d29daeSVladimir Kotal    if not os.path.isdir(source_dir):
176b2d29daeSVladimir Kotal        os.makedirs(source_dir)
177b2d29daeSVladimir Kotal
178b2d29daeSVladimir Kotal    with open(os.path.join(source_dir, "index.jsp"), "w+") as index:
179b2d29daeSVladimir Kotal        index.write("<% response.sendRedirect(\"/{}\"); %>".format(url_root))
180b2d29daeSVladimir Kotal
181b2d29daeSVladimir Kotal
182f7d6d77cSVladimir Kotaldef wait_for_tomcat(logger, uri):
183b2d29daeSVladimir Kotal    """
184b2d29daeSVladimir Kotal    Active/busy waiting for Tomcat to come up.
185b2d29daeSVladimir Kotal    Currently there is no upper time bound.
186b2d29daeSVladimir Kotal    """
187b2d29daeSVladimir Kotal    logger.info("Waiting for Tomcat to start")
188b2d29daeSVladimir Kotal
189b2d29daeSVladimir Kotal    while True:
190b2d29daeSVladimir Kotal        try:
191d44cbea0SVladimir Kotal            ret = get(uri)
192d44cbea0SVladimir Kotal            status = ret.status_code
193b2d29daeSVladimir Kotal        except ConnectionError:
194b2d29daeSVladimir Kotal            status = 0
195b2d29daeSVladimir Kotal
196b2d29daeSVladimir Kotal        if status != 200:
197b2d29daeSVladimir Kotal            logger.debug("Got status {} for {}, sleeping for 1 second".
198b2d29daeSVladimir Kotal                         format(status, uri))
199b2d29daeSVladimir Kotal            time.sleep(1)
200b2d29daeSVladimir Kotal        else:
201b2d29daeSVladimir Kotal            break
202b2d29daeSVladimir Kotal
203b2d29daeSVladimir Kotal    logger.info("Tomcat is ready")
204b2d29daeSVladimir Kotal
205b2d29daeSVladimir Kotal
206f7d6d77cSVladimir Kotaldef refresh_projects(logger, uri):
207b2d29daeSVladimir Kotal    """
208b2d29daeSVladimir Kotal    Ensure each immediate source root subdirectory is a project.
209b2d29daeSVladimir Kotal    """
210b2d29daeSVladimir Kotal    webapp_projects = list_projects(logger, uri)
2116100daaaSVladimir Kotal    if not webapp_projects:
2126100daaaSVladimir Kotal        return
2136100daaaSVladimir Kotal
214b2d29daeSVladimir Kotal    logger.debug('Projects from the web app: {}'.format(webapp_projects))
215b2d29daeSVladimir Kotal    src_root = OPENGROK_SRC_ROOT
216b2d29daeSVladimir Kotal
217b2d29daeSVladimir Kotal    # Add projects.
218b2d29daeSVladimir Kotal    for item in os.listdir(src_root):
219b2d29daeSVladimir Kotal        logger.debug('Got item {}'.format(item))
220b2d29daeSVladimir Kotal        if os.path.isdir(os.path.join(src_root, item)):
221b2d29daeSVladimir Kotal            if item not in webapp_projects:
222b2d29daeSVladimir Kotal                logger.info("Adding project {}".format(item))
223b2d29daeSVladimir Kotal                add_project(logger, item, uri)
224b2d29daeSVladimir Kotal
225b2d29daeSVladimir Kotal    # Remove projects
226b2d29daeSVladimir Kotal    for item in webapp_projects:
227b2d29daeSVladimir Kotal        if not os.path.isdir(os.path.join(src_root, item)):
228b2d29daeSVladimir Kotal            logger.info("Deleting project {}".format(item))
229b2d29daeSVladimir Kotal            delete_project(logger, item, uri)
230b2d29daeSVladimir Kotal
231b2d29daeSVladimir Kotal
232f7d6d77cSVladimir Kotaldef save_config(logger, uri, config_path):
233b2d29daeSVladimir Kotal    """
234b2d29daeSVladimir Kotal    Retrieve configuration from the web app and write it to file.
235788f14d3SVladimir Kotal    :param logger: logger instance
236b2d29daeSVladimir Kotal    :param uri: web app URI
237b2d29daeSVladimir Kotal    :param config_path: file path
238b2d29daeSVladimir Kotal    """
239b2d29daeSVladimir Kotal
240b2d29daeSVladimir Kotal    logger.info('Saving configuration to {}'.format(config_path))
241b2d29daeSVladimir Kotal    config = get_configuration(logger, uri)
242b2d29daeSVladimir Kotal    with open(config_path, "w+") as config_file:
243b2d29daeSVladimir Kotal        config_file.write(config)
244b2d29daeSVladimir Kotal
245b2d29daeSVladimir Kotal
246b2d29daeSVladimir Kotaldef merge_commands_env(commands, env):
247b2d29daeSVladimir Kotal    """
248b2d29daeSVladimir Kotal    Merge environment into command structure. If any of the commands has
249b2d29daeSVladimir Kotal    an environment already set, the env is merged in.
250b2d29daeSVladimir Kotal    :param commands: commands structure
251b2d29daeSVladimir Kotal    :param env: environment dictionary
252b2d29daeSVladimir Kotal    :return: updated commands structure
253b2d29daeSVladimir Kotal    """
254b2d29daeSVladimir Kotal    for cmd in commands:
255b2d29daeSVladimir Kotal        cmd_env = cmd.get('env')
256b2d29daeSVladimir Kotal        if cmd_env:
257b2d29daeSVladimir Kotal            cmd.env.update(env)
258b2d29daeSVladimir Kotal        else:
259b2d29daeSVladimir Kotal            cmd['env'] = env
260b2d29daeSVladimir Kotal
261b2d29daeSVladimir Kotal    return commands
262b2d29daeSVladimir Kotal
263b2d29daeSVladimir Kotal
2644810eb96SVladimir Kotaldef indexer_no_projects(logger, uri, config_path, sync_period,
2654810eb96SVladimir Kotal                        extra_indexer_options):
2664810eb96SVladimir Kotal    """
2674810eb96SVladimir Kotal    Project less indexer
2684810eb96SVladimir Kotal    """
2694810eb96SVladimir Kotal
2704810eb96SVladimir Kotal    wait_for_tomcat(logger, uri)
2714810eb96SVladimir Kotal
272f1835fddSVladimir Kotal    periodic_sync = True
273f1835fddSVladimir Kotal    if sync_period is None or sync_period == 0:
274f1835fddSVladimir Kotal        periodic_sync = False
275f1835fddSVladimir Kotal
2764810eb96SVladimir Kotal    while True:
2774810eb96SVladimir Kotal        indexer_options = ['-s', OPENGROK_SRC_ROOT,
2784810eb96SVladimir Kotal                           '-d', OPENGROK_DATA_ROOT,
2794810eb96SVladimir Kotal                           '-c', '/usr/local/bin/ctags',
2804810eb96SVladimir Kotal                           '--remote', 'on',
2814810eb96SVladimir Kotal                           '-H',
2824810eb96SVladimir Kotal                           '-W', config_path,
2834810eb96SVladimir Kotal                           '-U', uri]
2844810eb96SVladimir Kotal        if extra_indexer_options:
2854810eb96SVladimir Kotal            logger.debug("Adding extra indexer options: {}".
2864810eb96SVladimir Kotal                         format(extra_indexer_options))
2874810eb96SVladimir Kotal            indexer_options.extend(extra_indexer_options.split())
2884810eb96SVladimir Kotal        indexer = Indexer(indexer_options, logger=logger,
289d2f093daSVladimir Kotal                          jar=OPENGROK_JAR, doprint=True)
2904810eb96SVladimir Kotal        indexer.execute()
2914810eb96SVladimir Kotal
292f1835fddSVladimir Kotal        if periodic_sync:
2934810eb96SVladimir Kotal            sleep_seconds = sync_period * 60
2944810eb96SVladimir Kotal            logger.info("Sleeping for {} seconds".format(sleep_seconds))
2954810eb96SVladimir Kotal            time.sleep(sleep_seconds)
296b531e965SVladimir Kotal        elif not periodic_sync:
297b531e965SVladimir Kotal            sleep_event.wait()
2984810eb96SVladimir Kotal
2994810eb96SVladimir Kotal
3004810eb96SVladimir Kotaldef project_syncer(logger, loglevel, uri, config_path, sync_period,
3014810eb96SVladimir Kotal                   numworkers, env):
302b2d29daeSVladimir Kotal    """
303b2d29daeSVladimir Kotal    Wrapper for running opengrok-sync.
304b2d29daeSVladimir Kotal    To be run in a thread/process in the background.
305b2d29daeSVladimir Kotal    """
306b2d29daeSVladimir Kotal
307f7d6d77cSVladimir Kotal    wait_for_tomcat(logger, uri)
308b2d29daeSVladimir Kotal
3091822b591SVladimir Kotal    set_config_value(logger, 'projectsEnabled', 'true', uri)
3101822b591SVladimir Kotal
311f1835fddSVladimir Kotal    periodic_sync = True
312f1835fddSVladimir Kotal    if sync_period is None or sync_period == 0:
313f1835fddSVladimir Kotal        periodic_sync = False
314f1835fddSVladimir Kotal
315b2d29daeSVladimir Kotal    while True:
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)
3286100daaaSVladimir Kotal        if projects:
329b2d29daeSVladimir Kotal            #
330b2d29daeSVladimir Kotal            # The driveon=True is needed for the initial indexing of newly
3316100daaaSVladimir Kotal            # added project, otherwise the incoming check in the
3326100daaaSVladimir Kotal            # opengrok-mirror program would short circuit it.
333b2d29daeSVladimir Kotal            #
334b2d29daeSVladimir Kotal            if env:
335b2d29daeSVladimir Kotal                logger.info('Merging commands with environment')
336b2d29daeSVladimir Kotal                commands = merge_commands_env(config["commands"], env)
337b2d29daeSVladimir Kotal                logger.debug(config['commands'])
338b2d29daeSVladimir Kotal            else:
339b2d29daeSVladimir Kotal                commands = config["commands"]
340b2d29daeSVladimir Kotal
341b2d29daeSVladimir Kotal            logger.info("Sync starting")
342b2d29daeSVladimir Kotal            do_sync(loglevel, commands, config.get('cleanup'),
343b2d29daeSVladimir Kotal                    projects, config.get("ignore_errors"), uri,
344b2d29daeSVladimir Kotal                    numworkers, driveon=True, logger=logger, print_output=True)
345b2d29daeSVladimir Kotal            logger.info("Sync done")
346b2d29daeSVladimir Kotal
347b2d29daeSVladimir Kotal            # Workaround for https://github.com/oracle/opengrok/issues/1670
348b2d29daeSVladimir Kotal            Path(os.path.join(OPENGROK_DATA_ROOT, 'timestamp')).touch()
349b2d29daeSVladimir Kotal
350f7d6d77cSVladimir Kotal            save_config(logger, uri, config_path)
351b2d29daeSVladimir Kotal
352f1835fddSVladimir Kotal        if periodic_sync:
3533d5852a4SVladimir Kotal            sleep_seconds = sync_period * 60
354b2d29daeSVladimir Kotal            logger.info("Sleeping for {} seconds".format(sleep_seconds))
355b2d29daeSVladimir Kotal            time.sleep(sleep_seconds)
356b531e965SVladimir Kotal        elif not periodic_sync:
357b531e965SVladimir Kotal            sleep_event.wait()
358b2d29daeSVladimir Kotal
359b2d29daeSVladimir Kotal
3609d6d7c28SVladimir Kotaldef create_bare_config(logger, extra_indexer_options=None):
361b2d29daeSVladimir Kotal    """
362b2d29daeSVladimir Kotal    Create bare configuration file with a few basic settings.
363b2d29daeSVladimir Kotal    """
364b2d29daeSVladimir Kotal
365b2d29daeSVladimir Kotal    logger.info('Creating bare configuration in {}'.
366b2d29daeSVladimir Kotal                format(OPENGROK_CONFIG_FILE))
3674810eb96SVladimir Kotal    indexer_options = ['-s', OPENGROK_SRC_ROOT,
368b2d29daeSVladimir Kotal                       '-d', OPENGROK_DATA_ROOT,
369b2d29daeSVladimir Kotal                       '-c', '/usr/local/bin/ctags',
370b2d29daeSVladimir Kotal                       '--remote', 'on',
3714810eb96SVladimir Kotal                       '-H',
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)
3794810eb96SVladimir Kotal    indexer = Indexer(indexer_options,
3804810eb96SVladimir Kotal                      jar=OPENGROK_JAR,
381b2d29daeSVladimir Kotal                      logger=logger, doprint=True)
382b2d29daeSVladimir Kotal    indexer.execute()
383b2d29daeSVladimir Kotal    ret = indexer.getretcode()
384b2d29daeSVladimir Kotal    if ret != SUCCESS_EXITVAL:
385b2d29daeSVladimir Kotal        logger.error('Command returned {}'.format(ret))
386b2d29daeSVladimir Kotal        logger.error(indexer.geterroutput())
387b2d29daeSVladimir Kotal        raise Exception("Failed to create bare configuration")
388b2d29daeSVladimir Kotal
389b2d29daeSVladimir Kotal
390f1835fddSVladimir Kotaldef get_num_from_env(logger, env_name, default_value):
391f1835fddSVladimir Kotal    value = default_value
392f1835fddSVladimir Kotal    env_str = os.environ.get(env_name)
393f1835fddSVladimir Kotal    if env_str:
394f1835fddSVladimir Kotal        try:
395f1835fddSVladimir Kotal            n = int(env_str)
396f1835fddSVladimir Kotal            if n >= 0:
397f1835fddSVladimir Kotal                value = n
398f1835fddSVladimir Kotal        except ValueError:
399f1835fddSVladimir Kotal            logger.error("{} is not a number: {}".
400f1835fddSVladimir Kotal                         format(env_name, env_str))
401f1835fddSVladimir Kotal
402f1835fddSVladimir Kotal    return value
403f1835fddSVladimir Kotal
404f1835fddSVladimir Kotal
405*f750ae73SVladimir Kotaldef check_index_and_wipe_out(logger):
406*f750ae73SVladimir Kotal    """
407*f750ae73SVladimir Kotal    Check index by running the indexer. If the index does not match
408*f750ae73SVladimir Kotal    currently running version and the CHECK_INDEX environment variable
409*f750ae73SVladimir Kotal    is non empty, wipe out the directories under data root.
410*f750ae73SVladimir Kotal    """
411*f750ae73SVladimir Kotal    check_index = os.environ.get('CHECK_INDEX')
412*f750ae73SVladimir Kotal    if check_index and os.path.exists(OPENGROK_CONFIG_FILE):
413*f750ae73SVladimir Kotal        logger.info('Checking if index matches current version')
414*f750ae73SVladimir Kotal        indexer_options = ['-R', OPENGROK_CONFIG_FILE, '--checkIndex']
415*f750ae73SVladimir Kotal        indexer = Indexer(indexer_options, logger=logger,
416*f750ae73SVladimir Kotal                          jar=OPENGROK_JAR, doprint=True)
417*f750ae73SVladimir Kotal        indexer.execute()
418*f750ae73SVladimir Kotal        if indexer.getretcode() == 1:
419*f750ae73SVladimir Kotal            logger.info('Wiping out data root')
420*f750ae73SVladimir Kotal            root = OPENGROK_DATA_ROOT
421*f750ae73SVladimir Kotal            for entry in os.listdir(root):
422*f750ae73SVladimir Kotal                path = os.path.join(root, entry)
423*f750ae73SVladimir Kotal                if os.path.isdir(path):
424*f750ae73SVladimir Kotal                    try:
425*f750ae73SVladimir Kotal                        logger.info("Removing '{}'".format(path))
426*f750ae73SVladimir Kotal                        shutil.rmtree(path)
427*f750ae73SVladimir Kotal                    except Exception as e:
428*f750ae73SVladimir Kotal                        logger.error("cannot delete '{}': {}".format(path, e))
429*f750ae73SVladimir Kotal
430*f750ae73SVladimir Kotal
431f7d6d77cSVladimir Kotaldef main():
432b2d29daeSVladimir Kotal    log_level = os.environ.get('OPENGROK_LOG_LEVEL')
433b2d29daeSVladimir Kotal    if log_level:
434b2d29daeSVladimir Kotal        log_level = get_log_level(log_level)
435b2d29daeSVladimir Kotal    else:
436b2d29daeSVladimir Kotal        log_level = logging.INFO
437b2d29daeSVladimir Kotal
438b2d29daeSVladimir Kotal    logger = get_console_logger(get_class_basename(), log_level)
439b2d29daeSVladimir Kotal
440f7d6d77cSVladimir Kotal    uri, url_root = set_url_root(logger, os.environ.get('URL_ROOT'))
441f7d6d77cSVladimir Kotal    logger.debug("URL_ROOT = {}".format(url_root))
442f7d6d77cSVladimir Kotal    logger.debug("URI = {}".format(uri))
443b2d29daeSVladimir Kotal
44454289af4SVladimir Kotal    sync_period = get_num_from_env(logger, 'SYNC_PERIOD_MINUTES', 10)
4453d5852a4SVladimir Kotal    if sync_period == 0:
446b531e965SVladimir Kotal        logger.info("periodic synchronization disabled")
447b2d29daeSVladimir Kotal    else:
4483d5852a4SVladimir Kotal        logger.info("synchronization period = {} minutes".format(sync_period))
449b2d29daeSVladimir Kotal
450b2d29daeSVladimir Kotal    # Note that deploy is done before Tomcat is started.
451f7d6d77cSVladimir Kotal    deploy(logger, url_root)
452b2d29daeSVladimir Kotal
453f7d6d77cSVladimir Kotal    if url_root != '/source':
454f7d6d77cSVladimir Kotal        setup_redirect_source(logger, url_root)
455b2d29daeSVladimir Kotal
456b2d29daeSVladimir Kotal    env = {}
457dadacd3dSAdam Hornacek    extra_indexer_options = os.environ.get('INDEXER_OPT', '')
4584810eb96SVladimir Kotal    if extra_indexer_options:
4594810eb96SVladimir Kotal        logger.info("extra indexer options: {}".format(extra_indexer_options))
4604810eb96SVladimir Kotal        env['OPENGROK_INDEXER_OPTIONAL_ARGS'] = extra_indexer_options
461b2d29daeSVladimir Kotal    if os.environ.get('NOMIRROR'):
462b2d29daeSVladimir Kotal        env['OPENGROK_NO_MIRROR'] = os.environ.get('NOMIRROR')
463b2d29daeSVladimir Kotal    logger.debug('Extra environment: {}'.format(env))
464b2d29daeSVladimir Kotal
4654810eb96SVladimir Kotal    use_projects = True
4664810eb96SVladimir Kotal    if os.environ.get('AVOID_PROJECTS'):
4674810eb96SVladimir Kotal        use_projects = False
4684810eb96SVladimir Kotal
469b2d29daeSVladimir Kotal    #
470b2d29daeSVladimir Kotal    # Create empty configuration to avoid the non existent file exception
471b2d29daeSVladimir Kotal    # in the web app during the first web app startup.
472b2d29daeSVladimir Kotal    #
473b2d29daeSVladimir Kotal    if not os.path.exists(OPENGROK_CONFIG_FILE) or \
474b2d29daeSVladimir Kotal            os.path.getsize(OPENGROK_CONFIG_FILE) == 0:
475c6f6c3d1SVladimir Kotal        create_bare_config(logger, extra_indexer_options.split())
476b2d29daeSVladimir Kotal
477b9361335SVladimir Kotal    #
478*f750ae73SVladimir Kotal    # Index check needs read-only configuration so it is placed
479*f750ae73SVladimir Kotal    # right after create_bare_config().
480*f750ae73SVladimir Kotal    #
481*f750ae73SVladimir Kotal    check_index_and_wipe_out(logger)
482*f750ae73SVladimir Kotal
483*f750ae73SVladimir Kotal    #
484b9361335SVladimir Kotal    # If there is read-only configuration file, merge it with current
485b9361335SVladimir Kotal    # configuration.
486b9361335SVladimir Kotal    #
487b9361335SVladimir Kotal    read_only_config_file = os.environ.get('READONLY_CONFIG_FILE')
488b9361335SVladimir Kotal    if read_only_config_file and os.path.exists(read_only_config_file):
489b9361335SVladimir Kotal        logger.info('Merging read-only configuration from \'{}\' with current '
490b9361335SVladimir Kotal                    'configuration in \'{}\''.format(read_only_config_file,
491b9361335SVladimir Kotal                                                     OPENGROK_CONFIG_FILE))
492b9361335SVladimir Kotal        out_file = None
493b9361335SVladimir Kotal        with tempfile.NamedTemporaryFile(mode='w+', delete=False,
494b9361335SVladimir Kotal                                         prefix='merged_config') as tmp_out:
49570e4aafbSVladimir Kotal            out_file = tmp_out.name
496b9361335SVladimir Kotal            merge_config_files(read_only_config_file, OPENGROK_CONFIG_FILE,
497b9361335SVladimir Kotal                               tmp_out, jar=OPENGROK_JAR, loglevel=log_level)
498b9361335SVladimir Kotal
499b9361335SVladimir Kotal        if out_file and os.path.getsize(out_file) > 0:
500b9361335SVladimir Kotal            shutil.move(tmp_out.name, OPENGROK_CONFIG_FILE)
501b9361335SVladimir Kotal        else:
502b9361335SVladimir Kotal            logger.warning('Failed to merge read-only configuration, '
503b9361335SVladimir Kotal                           'leaving the original in place')
50470e4aafbSVladimir Kotal            if out_file:
50570e4aafbSVladimir Kotal                os.remove(out_file)
506b9361335SVladimir Kotal
5074810eb96SVladimir Kotal    if use_projects:
508f1835fddSVladimir Kotal        num_workers = get_num_from_env(logger, 'WORKERS',
509f1835fddSVladimir Kotal                                       multiprocessing.cpu_count())
510b2d29daeSVladimir Kotal        logger.info('Number of sync workers: {}'.format(num_workers))
511b2d29daeSVladimir Kotal
5124810eb96SVladimir Kotal        worker_function = project_syncer
5134810eb96SVladimir Kotal        syncer_args = (logger, log_level, uri,
514f7d6d77cSVladimir Kotal                       OPENGROK_CONFIG_FILE,
5154810eb96SVladimir Kotal                       sync_period, num_workers, env)
5164810eb96SVladimir Kotal    else:
5174810eb96SVladimir Kotal        worker_function = indexer_no_projects
5184810eb96SVladimir Kotal        syncer_args = (logger, uri, OPENGROK_CONFIG_FILE, sync_period,
5194810eb96SVladimir Kotal                       extra_indexer_options)
5204810eb96SVladimir Kotal
5214810eb96SVladimir Kotal    logger.debug("Starting sync thread")
5226100daaaSVladimir Kotal    sync_thread = threading.Thread(target=worker_function, name="Sync thread",
5236100daaaSVladimir Kotal                                   args=syncer_args, daemon=True)
5246100daaaSVladimir Kotal    sync_thread.start()
525b2d29daeSVladimir Kotal
526f1835fddSVladimir Kotal    rest_port = get_num_from_env(logger, 'REST_PORT', 5000)
527f1835fddSVladimir Kotal    token = os.environ.get('REST_TOKEN')
528f1835fddSVladimir Kotal    global expected_token
529f1835fddSVladimir Kotal    if token:
530f1835fddSVladimir Kotal        logger.debug("Setting expected token for REST endpoint"
531f1835fddSVladimir Kotal                     "on port {} to '{}'".format(rest_port, token))
532f1835fddSVladimir Kotal        expected_token = token
533f1835fddSVladimir Kotal    logger.debug("Starting REST thread")
5346100daaaSVladimir Kotal    rest_thread = threading.Thread(target=rest_function, name="REST thread",
5356100daaaSVladimir Kotal                                   args=(logger, rest_port), daemon=True)
5366100daaaSVladimir Kotal    rest_thread.start()
537f1835fddSVladimir Kotal
538b2d29daeSVladimir Kotal    # Start Tomcat last. It will be the foreground process.
539b2d29daeSVladimir Kotal    logger.info("Starting Tomcat")
5406100daaaSVladimir Kotal    global tomcat_popen
5416100daaaSVladimir Kotal    tomcat_popen = subprocess.Popen([os.path.join(tomcat_root, 'bin',
5426100daaaSVladimir Kotal                                                  'catalina.sh'),
5436100daaaSVladimir Kotal                                    'run'])
5446100daaaSVladimir Kotal    tomcat_popen.wait()
545f7d6d77cSVladimir Kotal
546f7d6d77cSVladimir Kotal
547f7d6d77cSVladimir Kotalif __name__ == "__main__":
5486100daaaSVladimir Kotal    try:
549f7d6d77cSVladimir Kotal        main()
5506100daaaSVladimir Kotal    except KeyboardInterrupt:
5516100daaaSVladimir Kotal        global tomcat_popen
5526100daaaSVladimir Kotal        print("Terminating Tomcat {}".format(tomcat_popen))
5536100daaaSVladimir Kotal        tomcat_popen.terminate()
5546100daaaSVladimir Kotal
5556100daaaSVladimir Kotal        sys.exit(1)
556