|
10 | 10 | """
|
11 | 11 | import os
|
12 | 12 | import sys
|
| 13 | +import gzip |
| 14 | +import json |
13 | 15 | import time
|
14 | 16 | import pprint
|
15 | 17 | import signal
|
| 18 | +import socket |
| 19 | +import getpass |
16 | 20 | import logging
|
17 | 21 | import argparse
|
18 | 22 | import threading
|
19 |
| -from typing import TYPE_CHECKING, Any, List, Type, Optional, cast |
| 23 | +from typing import TYPE_CHECKING, Any, Dict, List, Type, Tuple, Optional, cast |
20 | 24 |
|
21 | 25 | from .core.ssh import SshTunnelListener, SshHttpProtocolHandler
|
22 | 26 | from .core.work import ThreadlessPool
|
23 | 27 | from .core.event import EventManager
|
| 28 | +from .http.codes import httpStatusCodes |
24 | 29 | from .common.flag import FlagParser, flags
|
| 30 | +from .http.client import client |
25 | 31 | from .common.utils import bytes_
|
26 | 32 | from .core.work.fd import RemoteFdExecutor
|
| 33 | +from .http.methods import httpMethods |
27 | 34 | from .core.acceptor import AcceptorPool
|
28 | 35 | from .core.listener import ListenerPool
|
29 | 36 | from .core.ssh.base import BaseSshTunnelListener
|
| 37 | +from .common.plugins import Plugins |
| 38 | +from .common.version import __version__ |
30 | 39 | from .common.constants import (
|
31 |
| - IS_WINDOWS, DEFAULT_PLUGINS, DEFAULT_VERSION, DEFAULT_LOG_FILE, |
32 |
| - DEFAULT_PID_FILE, DEFAULT_LOG_LEVEL, DEFAULT_BASIC_AUTH, |
| 40 | + IS_WINDOWS, HTTPS_PROTO, DEFAULT_PLUGINS, DEFAULT_VERSION, |
| 41 | + DEFAULT_LOG_FILE, DEFAULT_PID_FILE, DEFAULT_LOG_LEVEL, DEFAULT_BASIC_AUTH, |
33 | 42 | DEFAULT_LOG_FORMAT, DEFAULT_WORK_KLASS, DEFAULT_OPEN_FILE_LIMIT,
|
34 | 43 | DEFAULT_ENABLE_DASHBOARD, DEFAULT_ENABLE_SSH_TUNNEL,
|
35 | 44 | DEFAULT_SSH_LISTENER_KLASS,
|
@@ -384,3 +393,98 @@ def main(**opts: Any) -> None:
|
384 | 393 |
|
385 | 394 | def entry_point() -> None:
|
386 | 395 | main()
|
| 396 | + |
| 397 | + |
| 398 | +def grout() -> None: # noqa: C901 |
| 399 | + default_grout_tld = os.environ.get('JAXL_DEFAULT_GROUT_TLD', 'jaxl.io') |
| 400 | + |
| 401 | + def _clear_line() -> None: |
| 402 | + print('\r' + ' ' * 60, end='', flush=True) |
| 403 | + |
| 404 | + def _env(scheme: bytes, host: bytes, port: int) -> Optional[Dict[str, Any]]: |
| 405 | + response = client( |
| 406 | + scheme=scheme, |
| 407 | + host=host, |
| 408 | + port=port, |
| 409 | + path=b'/env/', |
| 410 | + method=httpMethods.BIND, |
| 411 | + body='v={0}&u={1}&h={2}'.format( |
| 412 | + __version__, |
| 413 | + os.environ.get('USER', getpass.getuser()), |
| 414 | + socket.gethostname(), |
| 415 | + ).encode(), |
| 416 | + ) |
| 417 | + if response: |
| 418 | + if ( |
| 419 | + response.code is not None |
| 420 | + and int(response.code) == httpStatusCodes.OK |
| 421 | + and response.body is not None |
| 422 | + ): |
| 423 | + return cast( |
| 424 | + Dict[str, Any], |
| 425 | + json.loads( |
| 426 | + ( |
| 427 | + gzip.decompress(response.body).decode() |
| 428 | + if response.has_header(b'content-encoding') |
| 429 | + and response.header(b'content-encoding') == b'gzip' |
| 430 | + else response.body.decode() |
| 431 | + ), |
| 432 | + ), |
| 433 | + ) |
| 434 | + if response.code is None: |
| 435 | + _clear_line() |
| 436 | + print('\r\033[91mUnable to fetch\033[0m', end='', flush=True) |
| 437 | + else: |
| 438 | + _clear_line() |
| 439 | + print( |
| 440 | + '\r\033[91mError code {0}\033[0m'.format( |
| 441 | + response.code.decode(), |
| 442 | + ), |
| 443 | + end='', |
| 444 | + flush=True, |
| 445 | + ) |
| 446 | + else: |
| 447 | + _clear_line() |
| 448 | + print('\r\033[91mUnable to connect\033[0m') |
| 449 | + return None |
| 450 | + |
| 451 | + def _parse() -> Tuple[str, int]: |
| 452 | + """Here we deduce registry host/port based upon input parameters.""" |
| 453 | + parser = argparse.ArgumentParser(add_help=False) |
| 454 | + parser.add_argument('route', nargs='?', default=None) |
| 455 | + parser.add_argument('name', nargs='?', default=None) |
| 456 | + args, _remaining_args = parser.parse_known_args() |
| 457 | + grout_tld = default_grout_tld |
| 458 | + if args.name is not None and '.' in args.name: |
| 459 | + grout_tld = args.name.split('.', maxsplit=1)[1] |
| 460 | + grout_tld_parts = grout_tld.split(':') |
| 461 | + tld_host = grout_tld_parts[0] |
| 462 | + tld_port = 443 |
| 463 | + if len(grout_tld_parts) > 1: |
| 464 | + tld_port = int(grout_tld_parts[1]) |
| 465 | + return tld_host, tld_port |
| 466 | + |
| 467 | + tld_host, tld_port = _parse() |
| 468 | + env = None |
| 469 | + attempts = 0 |
| 470 | + try: |
| 471 | + while True: |
| 472 | + env = _env(scheme=HTTPS_PROTO, host=tld_host.encode(), port=int(tld_port)) |
| 473 | + attempts += 1 |
| 474 | + if env is not None: |
| 475 | + print('\rStarting ...' + ' ' * 30 + '\r', end='', flush=True) |
| 476 | + break |
| 477 | + time.sleep(1) |
| 478 | + _clear_line() |
| 479 | + print( |
| 480 | + '\rWaiting for connection {0}'.format('.' * (attempts % 4)), |
| 481 | + end='', |
| 482 | + flush=True, |
| 483 | + ) |
| 484 | + time.sleep(1) |
| 485 | + except KeyboardInterrupt: |
| 486 | + sys.exit(1) |
| 487 | + |
| 488 | + assert env is not None |
| 489 | + print('\r' + ' ' * 70 + '\r', end='', flush=True) |
| 490 | + Plugins.from_bytes(env['m'].encode(), name='client').grout(env=env['e']) # type: ignore[attr-defined] |
0 commit comments