blob: fccb2de76805e146b0e5ed717c4e8f9711a82f0c [file] [log] [blame]
#!/usr/bin/env python3
# Copyright (C) 2020 Apple Inc. All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
# 1. Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND ANY
# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY
# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
# ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
import argparse
import json
import math
import os
import subprocess
import sys
import time
import reporelaypy
import webkitcorepy
scripts = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
if os.path.isdir(os.path.join(scripts, 'webkitpy')):
sys.path.insert(0, scripts)
import webkitpy
from reporelaypy import Checkout, Database, HookProcessor, HookReceiver, Redirector
from webkitcorepy import arguments, AutoInstall
from whichcraft import which
def main(args=None):
parser = argparse.ArgumentParser(description='Run a git relay server for testing')
group = parser.add_argument_group('Repository')
group.add_argument(
'--path', '-p', '-C',
dest='path', default=os.getcwd(), action='store',
help='Set the repository path to be used',
)
group.add_argument(
'--http-proxy', dest='http_proxy', default=None, action='store',
help='Set an http proxy',
)
group.add_argument(
'--url', dest='url', default=None, action='store',
help='Set the repository path to be used',
)
parser.add_argument(
'--sentinal', '--no-sentinal', action=arguments.NoAction, dest='sentinal', default=False,
help='Lay down sentinal file on disk to determine checkout state.',
)
group.add_argument(
'--update', dest='update_interval', default=0, action='store', type=int,
help='Set the number of seconds to wait between polling git checkout',
)
group.add_argument(
'--fallback', dest='fallback', default=None, action='store', type=str,
help='Fallback repository URL',
)
group.add_argument(
'--remote', dest='remotes', action='append',
help="Add an additional remote to forward changes to, formatted as 'name:url'",
)
group = parser.add_argument_group('Database')
group.add_argument(
'--redis-host', dest='redis_host', default=None, action='store',
help="Set the hostname of the Redis database to be used. (Environment variable '{}' used by default)".format(Database.HOST_ENV),
)
group.add_argument(
'--redis-password', dest='redis_password', default=None, action='store',
help="Set the password of the Redis database to be used. (Environment variable '{}' used by default)".format(Database.PASSWORD_ENV),
)
group.add_argument(
'--redis-expiration', dest='redis_expiration', default=Database.DEFAULT_EXPIRATION, action='store', type=int,
help="Set the default Redis expiration time in seconds",
)
group = parser.add_argument_group('Webserver')
group.add_argument(
'--port', dest='port', default=5000, action='store',
help='Port to expose webserver on.',
)
group.add_argument(
'--poll', dest='poll', default=10, action='store',
help='Number of seconds between polls of the webserver process.',
)
group.add_argument(
'--redirector', '-r', dest='redirector', action='append',
help='Base URL to redirect user to for commit information.',
)
group = parser.add_argument_group('Hooks')
group.add_argument(
'--hooks', '--no-hooks', action=arguments.NoAction, dest='hooks', default=False,
help='Enable or disable hook end-points (disabled by default)',
)
group.add_argument(
'--debug', '--no-debug', action=arguments.NoAction, dest='hooks_debug', default=True,
help='Enable endpoint to report hooks being processed (enabled by default)',
)
args = parser.parse_args(args=args)
database = Database(host=args.redis_host, password=args.redis_password)
print('Constructed database with parameters:')
print(' Host: {}'.format(database.host or 'None (in-memory fake database)'))
if database.password:
print(' Password: {}'.format(len(database.password) * '*'))
if database.default_expiration:
print(' Expiration: {} hours'.format(database.default_expiration / (60.0 * 60.0)))
print('Connecting to database...')
database.ping()
print('Connected to database!')
remotes = {}
for pair in args.remotes:
name, remote = pair.split(':', 1)
if not remote:
sys.stderr("Invalid forwarding remote '{}'".format(pair))
return 1
remotes[name] = remote
print('Finding checkout...')
checkout = Checkout(
path=args.path,
url=args.url,
http_proxy=args.http_proxy,
sentinal=args.sentinal,
fallback_url=args.fallback,
remotes=remotes,
)
print('Git checkout:')
print(' Path: {}'.format(checkout.path))
print(' URL: {}'.format(checkout.url))
if checkout.fallback_repository:
print(' Fallback: {}'.format(checkout.fallback_repository.url))
if not checkout.repository:
print(' Cloning repository...')
if args.update_interval:
print(' Polling checkout with interval of {} seconds, doing first poll now...'.format(args.update_interval))
checkout.update_all()
if remotes:
print('Forwarding updates to:')
for name, url in remotes.items():
print(' {}: {}'.format(name, url))
if args.redirector:
print('User specified the following redirect urls:')
for url in args.redirector:
redirector = Redirector(url)
print(' {}: {}'.format(redirector.name, redirector.url))
passenv = dict(
PYTHONPATH=':'.join(sys.path),
CHECKOUT=json.dumps(checkout, cls=Checkout.Encoder),
REDIRECTORS=json.dumps([Redirector(url) for url in args.redirector or []], cls=Redirector.Encoder),
HOOKS=json.dumps({'enabled': args.hooks, 'debug': args.hooks_debug}),
)
if AutoInstall.directory:
passenv['AUTOINSTALL_PATH'] = AutoInstall.directory
if database.host:
passenv[database.HOST_ENV] = database.host
if database.password:
passenv[database.PASSWORD_ENV] = database.password
if database.default_expiration:
passenv[database.EXPIRATION_ENV] = str(database.default_expiration)
if os.environ.get(HookReceiver.SECRET_ENV):
passenv[HookReceiver.SECRET_ENV] = os.environ.get(HookReceiver.SECRET_ENV)
processor = HookProcessor(checkout=checkout, database=database) if args.hooks else None
with subprocess.Popen(
[which('gunicorn'), 'reporelaypy.webserver:app'],
cwd=os.path.dirname(os.path.dirname(reporelaypy.__file__)),
env=passenv,
) as webserver:
last_poll = time.time()
last_pull = time.time()
while True:
if last_poll + args.poll < time.time():
if webserver.poll() is None:
break
last_poll = time.time()
if last_pull + args.update_interval < time.time():
processor.process_hooks() if processor else checkout.update_all()
last_pull = time.time()
time.sleep(math.gcd(args.poll, args.update_interval))
return 0
if '__main__' == __name__:
sys.exit(main(args=sys.argv[1:]))