runbot/runbot_merge/ngrok

95 lines
2.5 KiB
Plaintext
Raw Permalink Normal View History

#!/usr/bin/env python
import contextlib
import random
import signal
import subprocess
import threading
import time
import requests
import sys
port = int(sys.argv[1])
NGROK_CLI = [
'ngrok', 'start', '--none', '--region', 'eu',
]
own = None
web_addr = 'http://localhost:4040/api'
addr = 'localhost:%d' % port
# FIXME: this is for xdist to avoid workers running ngrok at the
# exact same time, use lockfile instead
time.sleep(random.SystemRandom().randint(1, 10))
# try to find out if ngrok is running, and if it's not attempt
# to start it
try:
requests.get(web_addr)
except requests.exceptions.ConnectionError:
own = subprocess.Popen(NGROK_CLI, stdout=subprocess.DEVNULL)
for _ in range(5):
time.sleep(1)
with contextlib.suppress(requests.exceptions.ConnectionError):
requests.get(web_addr)
break
else:
sys.exit("Unable to connect to ngrok")
requests.post(f'{web_addr}/tunnels', json={
'name': str(port),
'proto': 'http',
'addr': addr,
'schemes': ['https'],
'inspect': True,
}).raise_for_status()
tunnel = f'{web_addr}/tunnels/{port}'
for _ in range(10):
time.sleep(2)
r = requests.get(tunnel)
# not created yet, wait and retry
if r.status_code == 404:
continue
# check for weird responses
r.raise_for_status()
print(r.json()['public_url'], flush=True)
sys.stdout.close()
break
else:
sys.exit("ngrok tunnel creation failed (?)")
shutdown = threading.Event()
def cleanup(_sig, _frame):
requests.delete(tunnel)
for _ in range(10):
time.sleep(1)
r = requests.get(tunnel)
# check if deletion is done
if r.status_code == 404:
break
r.raise_for_status()
else:
raise sys.exit("ngrok tunnel deletion failed")
r = requests.get(f'{web_addr}/tunnels')
if not r.ok:
sys.exit(f'{r.reason} {r.text}')
# FIXME: if we started ngrok, we should probably wait for all tunnels to be
# closed then terminate ngrok? This is likely a situation where the
# worker which created the ngrok instance finished early...
if own and not r.json()['tunnels']:
# no more tunnels and we started ngrok -> try to kill it
own.terminate()
own.wait(30)
shutdown.set()
# don't know why but signal.sigwait doesn't seem to take SIGTERM in account so
# we need the cursed version
signal.signal(signal.SIGTERM, cleanup)
signal.signal(signal.SIGINT, cleanup)
shutdown.wait()