def test_args(): arg_parser = ArgumentParser(description="resoto metrics exporter", env_args_prefix="RESOTOMETRICS_") add_args(arg_parser) arg_parser.parse_args() assert resotocore.http_uri == "https://localhost:8900" assert resotocore.ws_uri == "wss://localhost:8900"
def test_args(): arg_parser = ArgumentParser( description="resoto worker", env_args_prefix="RESOTOWORKER_", ) add_args(arg_parser) core_add_args(arg_parser) arg_parser.parse_args() assert resotocore.http_uri == "https://localhost:8900"
def test_args(): arg_parser = ArgumentParser(description="resoto metrics exporter", env_args_prefix="RESOTOMETRICS_") add_args(arg_parser) arg_parser.parse_args() assert ArgumentParser.args.resotocore_uri == "http://localhost:8900" assert ArgumentParser.args.resotocore_ws_uri == "ws://localhost:8900" assert ArgumentParser.args.resotocore_graph == "resoto" assert ArgumentParser.args.timeout == 300
def main() -> None: setup_logger("resotometrics") signal(SIGINT, handler) signal(SIGTERM, handler) arg_parser = ArgumentParser(description="resoto metrics exporter", env_args_prefix="RESOTOMETRICS_") add_args(arg_parser) logging_add_args(arg_parser) jwt_add_args(arg_parser) WebServer.add_args(arg_parser) WebApp.add_args(arg_parser) arg_parser.parse_args() metrics = Metrics() graph_collector = GraphCollector(metrics) REGISTRY.register(graph_collector) base_uri = ArgumentParser.args.resotocore_uri.strip("/") resotocore_graph = ArgumentParser.args.resotocore_graph graph_uri = f"{base_uri}/graph/{resotocore_graph}" query_uri = f"{graph_uri}/query/aggregate?section=reported" message_processor = partial(core_actions_processor, metrics, query_uri) core_actions = CoreActions( identifier="resotometrics", resotocore_uri=ArgumentParser.args.resotocore_uri, resotocore_ws_uri=ArgumentParser.args.resotocore_ws_uri, actions={ "generate_metrics": { "timeout": ArgumentParser.args.timeout, "wait_for_completion": True, }, }, message_processor=message_processor, ) web_server = WebServer(WebApp()) web_server.daemon = True web_server.start() core_actions.start() shutdown_event.wait() web_server.shutdown() core_actions.shutdown() sys.exit(0)
def test_web(): arg_parser = ArgumentParser( description="resoto metrics exporter", env_args_prefix="RESOTOMETRICS_" ) WebServer.add_args(arg_parser) WebApp.add_args(arg_parser) arg_parser.parse_args() # Find a free local port to reuse when we bind the web server. # This is so that multiple builds/tests can run in parallel # on the same CI agent. tcp = socket.socket(socket.AF_INET, socket.SOCK_STREAM) tcp.bind(("", 0)) _, free_port = tcp.getsockname() ArgumentParser.args.web_port = free_port tcp.close() # todo: race between closing socket and reusing free port in WebServer web_server = WebServer(WebApp()) web_server.daemon = True web_server.start() start_time = time.time() while not web_server.serving: if time.time() - start_time > 10: raise RuntimeError("timeout waiting for web server start") time.sleep(0.1) # We're statically using localhost in the endpoint url. # Other options would have been to set ArgumentParser.args.web_host # and then connect to that value. However we'd have to use an IP # address and then needed to decide if we use either # 127.0.0.1 or ::1. Which might fail on CI boxes without # IPv4 or IPv6 respectively. Instead we leave the default which # binds to all IPs and assume that localhost will resolve to # the appropriate v4 or v6 loopback address. A disadvantage # of this is that for a brief moment during the test we're # exposing the web server on all local IPs. endpoint = f"http://localhost:{ArgumentParser.args.web_port}" r = requests.get(f"{endpoint}/health") assert r.content == b"ok\r\n" web_server.shutdown()
def parse_args(args: Optional[List[str]] = None) -> Namespace: parser = ArgumentParser( env_args_prefix="RESOTOEVENTLOG_", description="Resoto Log Aggregator.", ) jwt.add_args(parser) parser.add_argument("--no-tls", default=False, action="store_true", help="Disable TLS and use plain HTTP.") parser.add_argument("--cert", help="Path to custom certificate file") parser.add_argument("--cert-key", help="Path to custom certificate key file") parser.add_argument("--cert-key-pass", help="Passphrase for certificate key file") parser.add_argument("--verbose", action="store_true", help="Enable verbose logging.") parser.add_argument("--debug", action="store_true", help="Enable verbose logging.") parser.add_argument("--max-queued-entries", type=int, default=1000) parser.add_argument("--version", action="store_true", help="Show version.") parser.add_argument("--host", default="0.0.0.0", help="Host to listen on.") parser.add_argument("--port", default=8901, type=int, help="Port to listen on.") parser.add_argument( "--resotocore-uri", dest="resotocore_uri", default="https://localhost:8900", help="Resoto Core URI.", ) parsed: Namespace = parser.parse_args(args if args else []) if parsed.version: print(f"resotoeventlog {version()}") sys.exit(0) return parsed
def parse_args(args: Optional[List[str]] = None, namespace: Optional[str] = None) -> Namespace: def is_file(message: str) -> Callable[[str], str]: def check_file(path: str) -> str: if os.path.isfile(path): return path else: raise AttributeError(f"{message}: path {path} is not a directory!") return check_file def is_dir(message: str) -> Callable[[str], str]: def check_dir(path: str) -> str: if os.path.isdir(path): return path else: raise AttributeError(f"{message}: path {path} is not a directory!") return check_dir def is_url(message: str) -> Callable[[str], str]: def check_url(url: str) -> str: try: urlparse(url) return url except ValueError as ex: raise AttributeError(f"{message}: url {url} can not be parsed!") from ex return check_url parser = ArgumentParser( env_args_prefix="RESOTOCORE_", description="Maintains graphs of resources of any shape.", epilog="Keeps all the things.", ) jwt_add_args(parser) parser.add_argument( "--log-level", default="info", help="Log level (default: info)", ) parser.add_argument( "--graphdb-server", default="http://localhost:8529", dest="graphdb_server", help="Graph database server (default: http://localhost:8529)", ) parser.add_argument( "--graphdb-database", default="resoto", dest="graphdb_database", help="Graph database name (default: resoto)", ) parser.add_argument( "--graphdb-username", default="resoto", dest="graphdb_username", help="Graph database login (default: resoto)", ) parser.add_argument( "--graphdb-password", default="", dest="graphdb_password", help='Graph database password (default: "")', ) parser.add_argument( "--graphdb-root-password", default="", dest="graphdb_root_password", help="Graph root database password used for creating user and database if not existent.", ) parser.add_argument( "--graphdb-bootstrap-do-not-secure", default=False, action="store_true", dest="graphdb_bootstrap_do_not_secure", help="Leave an empty root password during system setup process.", ) parser.add_argument( "--graphdb-type", default="arangodb", dest="graphdb_type", help="Graph database type (default: arangodb)", ) parser.add_argument( "--graphdb-no-ssl-verify", action="store_true", dest="graphdb_no_ssl_verify", help="If the connection should not be verified (default: False)", ) parser.add_argument( "--graphdb-request-timeout", type=int, default=900, dest="graphdb_request_timeout", help="Request timeout in seconds (default: 900)", ) parser.add_argument( "--plantuml-server", default="http://plantuml.resoto.org:8080", help="PlantUML server URI for UML image rendering.", ) parser.add_argument( "--host", type=str, default="localhost", nargs="+", help="TCP host(s) to bind on (default: localhost)", ) parser.add_argument( "--port", type=int, default=8900, help="TCP port to bind on (default: 8900)", ) parser.add_argument( "--merge_max_wait_time_seconds", type=int, default=3600, help="Max waiting time to complete a merge graph action.", ) parser.add_argument("--debug", default=False, action="store_true", help=argparse.SUPPRESS) parser.add_argument( "--analytics-opt-out", default=False, action="store_true", help="Stop collecting analytics data.", ) parser.add_argument( "--ui-path", type=is_dir("can not parse --ui-dir"), help="The directory where the UI is installed. This directory will be served under /ui/.", ) parser.add_argument( "--tsdb-proxy-url", type=is_url("can not parse --tsdb-proxy-url"), help="The url to the time series database. This path will be served under /tsdb/.", ) parser.add_argument( "--tls-cert", type=is_file("can not parse --tls-cert"), help="Path to a single file in PEM format containing the certificate as well as any number " "of CA certificates needed to establish the certificate’s authenticity.", ) parser.add_argument( "--tls-key", type=is_file("can not parse --tls-key"), help="Path to a file containing the private key. " "If not defined the private key will be taken from certfile as well.", ) parser.add_argument( "--tls-password", type=str, help="Optional password to decrypt the private key file.", ) parser.add_argument( "--cli-default-graph", type=str, default="resoto", dest="cli_default_graph", help="Use this graph for CLI actions, if no graph is specified explicitly.", ) parser.add_argument( "--cli-default-section", type=str, default="reported", dest="cli_default_section", help="Use this graph section by default, if no section is specified." "Relative paths will be interpreted with respect to this section.", ) parser.add_argument("--version", action="store_true", help="Print the version of resotocore and exit.") parser.add_argument( "--jobs", nargs="*", type=argparse.FileType("r"), help="Read job definitions from given file.", ) parser.add_argument( "--start-collect-on-subscriber-connect", default=False, action="store_true", help="Start the collect workflow, when the first handling actor connects to the system.", ) parser.add_argument( "---graph-update-abort-after", dest="graph_updates_abort_after", default="4h", type=parse_duration, help="If a graph update takes longer than this duration, the update is aborted.", ) parsed: Namespace = parser.parse_args(args if args else [], namespace) if parsed.version: # print here on purpose, since logging is not set up yet. print(f"resotocore {version()}") sys.exit(0) return parsed
def main() -> None: setup_logger("resotoworker") # Try to run in a new process group and # ignore if not possible for whatever reason try: os.setpgid(0, 0) except Exception: pass resotolib.signal.parent_pid = os.getpid() # Add cli args # The following double parsing of cli args is done so that when # a user specifies e.g. `--collector aws --help` they would # no longer be shown cli args for other collectors like gcp. collector_arg_parser = ArgumentParser( description="resoto worker", env_args_prefix="RESOTOWORKER_", add_help=False, add_machine_help=False, ) PluginLoader.add_args(collector_arg_parser) (args, _) = collector_arg_parser.parse_known_args() ArgumentParser.args = args arg_parser = ArgumentParser( description="resoto worker", env_args_prefix="RESOTOWORKER_", ) jwt_add_args(arg_parser) logging_add_args(arg_parser) graph_add_args(arg_parser) collect_add_args(arg_parser) cleanup_add_args(arg_parser) core_add_args(arg_parser) resotocore_add_args(arg_parser) CoreActions.add_args(arg_parser) WebApp.add_args(arg_parser) PluginLoader.add_args(arg_parser) event_add_args(arg_parser) add_args(arg_parser) # Find resoto Plugins in the resoto.plugins module plugin_loader = PluginLoader() plugin_loader.add_plugin_args(arg_parser) # At this point the CLI, all Plugins as well as the WebServer have # added their args to the arg parser arg_parser.parse_args() # Handle Ctrl+c and other means of termination/shutdown resotolib.signal.initializer() add_event_listener(EventType.SHUTDOWN, shutdown, blocking=False) # Try to increase nofile and nproc limits increase_limits() web_server = WebServer(WebApp()) web_server.daemon = True web_server.start() core_actions = CoreActions( identifier=f"{ArgumentParser.args.resotocore_subscriber_id}-collect_cleanup", resotocore_uri=ArgumentParser.args.resotocore_uri, resotocore_ws_uri=ArgumentParser.args.resotocore_ws_uri, actions={ "collect": { "timeout": ArgumentParser.args.timeout, "wait_for_completion": True, }, "cleanup": { "timeout": ArgumentParser.args.timeout, "wait_for_completion": True, }, }, message_processor=partial( core_actions_processor, plugin_loader.plugins(PluginType.COLLECTOR) ), ) task_queue_filter = {} if ArgumentParser.args.collector and len(ArgumentParser.args.collector) > 0: task_queue_filter = {"cloud": list(ArgumentParser.args.collector)} core_tasks = CoreTasks( identifier="workerd-tasks", resotocore_ws_uri=ArgumentParser.args.resotocore_ws_uri, tasks=["tag"], task_queue_filter=task_queue_filter, message_processor=core_tag_tasks_processor, ) core_actions.start() core_tasks.start() for Plugin in plugin_loader.plugins(PluginType.ACTION): try: log.debug(f"Starting action plugin {Plugin}") plugin = Plugin() plugin.start() except Exception as e: log.exception(f"Caught unhandled persistent Plugin exception {e}") # We wait for the shutdown Event to be set() and then end the program # While doing so we print the list of active threads once per 15 minutes shutdown_event.wait() web_server.shutdown() time.sleep(1) # everything gets 1000ms to shutdown gracefully before we force it resotolib.signal.kill_children(resotolib.signal.SIGTERM, ensure_death=True) log.info("Shutdown complete") os._exit(0)
def main() -> None: setup_logger("resotoshell") shutdown_event = Event() arg_parser = ArgumentParser(description="resoto shell", env_args_prefix="RESOTOSHELL_") add_args(arg_parser) logging_add_args(arg_parser) jwt_add_args(arg_parser) arg_parser.parse_args() headers = {"Accept": "text/plain"} execute_endpoint = f"{ArgumentParser.args.resotocore_uri}/cli/execute" execute_endpoint += f"?resoto_session_id={rnd_str()}" if ArgumentParser.args.resotocore_graph: query_string = urlencode( {"graph": ArgumentParser.args.resotocore_graph}) execute_endpoint += f"&{query_string}" if ArgumentParser.args.resotocore_section: query_string = urlencode( {"section": ArgumentParser.args.resotocore_section}) execute_endpoint += f"&{query_string}" if ArgumentParser.args.stdin: shell = Shell(execute_endpoint, False, "monochrome") log.debug("Reading commands from STDIN") try: for command in sys.stdin.readlines(): command = command.rstrip() shell.handle_command(command, headers) except KeyboardInterrupt: pass except (RuntimeError, ValueError) as e: log.error(e) except Exception: log.exception( "Caught unhandled exception while processing CLI command") finally: shutdown_event.set() else: shell = Shell(execute_endpoint, True, detect_color_system()) completer = None history_file = str(pathlib.Path.home() / ".resotoshell_history") history = FileHistory(history_file) session = PromptSession(history=history) log.debug("Starting interactive session") while not shutdown_event.is_set(): try: command = session.prompt("> ", completer=completer) if command == "": continue if command == "quit": shutdown_event.set() continue shell.handle_command(command, headers) except KeyboardInterrupt: pass except EOFError: shutdown_event.set() except (RuntimeError, ValueError) as e: log.error(e) except Exception: log.exception( "Caught unhandled exception while processing CLI command") sys.exit(0)
def main() -> None: setup_logger("resotoworker") # Try to run in a new process group and # ignore if not possible for whatever reason try: os.setpgid(0, 0) except Exception: pass resotolib.proc.parent_pid = os.getpid() arg_parser = ArgumentParser( description="resoto worker", env_args_prefix="RESOTOWORKER_", ) add_args(arg_parser) jwt_add_args(arg_parser) logging_add_args(arg_parser) core_add_args(arg_parser) Config.add_args(arg_parser) TLSData.add_args(arg_parser) # Find resoto Plugins in the resoto.plugins module plugin_loader = PluginLoader() plugin_loader.add_plugin_args(arg_parser) # At this point the CLI, all Plugins as well as the WebServer have # added their args to the arg parser arg_parser.parse_args() try: wait_for_resotocore(resotocore.http_uri) except TimeoutError as e: log.fatal(f"Failed to connect to resotocore: {e}") sys.exit(1) tls_data = None if resotocore.is_secure: tls_data = TLSData( common_name=ArgumentParser.args.subscriber_id, resotocore_uri=resotocore.http_uri, ) tls_data.start() config = Config( ArgumentParser.args.subscriber_id, resotocore_uri=resotocore.http_uri, tls_data=tls_data, ) add_config(config) plugin_loader.add_plugin_config(config) config.load_config() def send_request(request: requests.Request) -> requests.Response: prepared = request.prepare() s = requests.Session() verify = None if tls_data: verify = tls_data.verify return s.send(request=prepared, verify=verify) core = Resotocore(send_request, config) collector = Collector(core.send_to_resotocore, config) # Handle Ctrl+c and other means of termination/shutdown resotolib.proc.initializer() add_event_listener(EventType.SHUTDOWN, shutdown, blocking=False) # Try to increase nofile and nproc limits increase_limits() web_server_args = {} if tls_data: web_server_args = { "ssl_cert": tls_data.cert_path, "ssl_key": tls_data.key_path, } web_server = WebServer( WebApp(mountpoint=Config.resotoworker.web_path), web_host=Config.resotoworker.web_host, web_port=Config.resotoworker.web_port, **web_server_args, ) web_server.daemon = True web_server.start() core_actions = CoreActions( identifier=f"{ArgumentParser.args.subscriber_id}-collector", resotocore_uri=resotocore.http_uri, resotocore_ws_uri=resotocore.ws_uri, actions={ "collect": { "timeout": Config.resotoworker.timeout, "wait_for_completion": True, }, "cleanup": { "timeout": Config.resotoworker.timeout, "wait_for_completion": True, }, }, message_processor=partial(core_actions_processor, plugin_loader, tls_data, collector), tls_data=tls_data, ) task_queue_filter = {} if len(Config.resotoworker.collector) > 0: task_queue_filter = {"cloud": list(Config.resotoworker.collector)} core_tasks = CoreTasks( identifier=f"{ArgumentParser.args.subscriber_id}-tagger", resotocore_ws_uri=resotocore.ws_uri, tasks=["tag"], task_queue_filter=task_queue_filter, message_processor=core_tag_tasks_processor, tls_data=tls_data, ) core_actions.start() core_tasks.start() for Plugin in plugin_loader.plugins(PluginType.ACTION): try: log.debug(f"Starting action plugin {Plugin}") plugin = Plugin(tls_data=tls_data) plugin.start() except Exception as e: log.exception(f"Caught unhandled persistent Plugin exception {e}") # We wait for the shutdown Event to be set() and then end the program # While doing so we print the list of active threads once per 15 minutes shutdown_event.wait() web_server.shutdown() time.sleep(1) # everything gets 1000ms to shutdown gracefully before we force it resotolib.proc.kill_children(resotolib.proc.SIGTERM, ensure_death=True) log.info("Shutdown complete") os._exit(0)
def test_args(): arg_parser = ArgumentParser(description="resoto Shell", env_args_prefix="RESOTOSHELL_") add_args(arg_parser) arg_parser.parse_args() assert ArgumentParser.args.resotocore_uri == "http://localhost:8900"
def parse_args(args: Optional[List[str]] = None) -> Namespace: def is_file(message: str) -> Callable[[str], str]: def check_file(path: str) -> str: if os.path.isfile(path): return path else: raise AttributeError( f"{message}: path {path} is not a directory!") return check_file def is_dir(message: str) -> Callable[[str], str]: def check_dir(path: str) -> str: if os.path.isdir(path): return path else: raise AttributeError( f"{message}: path {path} is not a directory!") return check_dir def key_value(kv: str) -> Tuple[str, JsonElement]: try: key, value = path_json_value_parser.parse(kv) return (key, value[0]) if len(value) == 1 else (key, value) except Exception as ex: raise AttributeError( f"Can not parse config option: {kv}. Reason: {ex}") from ex parser = ArgumentParser( env_args_prefix="RESOTOCORE_", description="Maintains graphs of resources of any shape.", epilog="Keeps all the things.", ) jwt_add_args(parser) parser.add_argument( "--graphdb-server", default="http://localhost:8529", dest="graphdb_server", help="Graph database server (default: http://localhost:8529)", ) parser.add_argument("--graphdb-database", default="resoto", dest="graphdb_database", help="Graph database name (default: resoto)") parser.add_argument("--graphdb-username", default="resoto", dest="graphdb_username", help="Graph database login (default: resoto)") parser.add_argument("--graphdb-password", default="", dest="graphdb_password", help='Graph database password (default: "")') parser.add_argument( "--graphdb-root-password", default="", dest="graphdb_root_password", help= "Graph root database password used for creating user and database if not existent.", ) parser.add_argument( "--graphdb-bootstrap-do-not-secure", default=False, action="store_true", dest="graphdb_bootstrap_do_not_secure", help="Leave an empty root password during system setup process.", ) parser.add_argument("--graphdb-type", default="arangodb", dest="graphdb_type", help="Graph database type (default: arangodb)") parser.add_argument( "--graphdb-no-ssl-verify", action="store_true", dest="graphdb_no_ssl_verify", help="If the connection should not be verified (default: False)", ) parser.add_argument( "--graphdb-request-timeout", type=int, default=900, dest="graphdb_request_timeout", help="Request timeout in seconds (default: 900)", ) parser.add_argument("--no-tls", default=False, action="store_true", help="Disable TLS and use plain HTTP.") parser.add_argument( "--cert", type=is_file("can not parse --cert"), dest="cert", help= "Path to a single file in PEM format containing the host certificate. " "If no certificate is provided, it is created using the CA.", ) parser.add_argument( "--cert-key", type=is_file("can not parse --cert-key"), dest="cert_key", help= "In case a --cert is provided. Path to a file containing the private key.", ) parser.add_argument( "--cert-key-pass", dest="cert_key_pass", type=str, help= "In case a --cert is provided. Optional password to decrypt the private key file.", ) parser.add_argument( "--ca-cert", type=is_file("can not parse --ca-cert"), dest="ca_cert", help= "Path to a single file in PEM format containing the CA certificate.", ) parser.add_argument( "--ca-cert-key", type=is_file("can not parse --ca-cert-key"), dest="ca_cert_key", help="Path to a file containing the private key for the CA certificate. " "New certificates can be created when a CA certificate and private key is provided. " "Without the private key, the CA certificate is only used for outgoing http requests.", ) parser.add_argument( "--ca-cert-key-pass", dest="ca_cert_key_pass", type=str, help="Optional password to decrypt the private ca-cert-key file.", ) parser.add_argument("--version", action="store_true", help="Print the version of resotocore and exit.") parser.add_argument( "--override", "-o", nargs="+", type=key_value, dest="config_override", default=[], help= "Override configuration parameters. Format: path.to.property=value. " "The existing configuration will be patched with the provided values. " "A value can be a simple value or a comma separated list of values if a list is required. " "Note: this argument allows multiple overrides separated by space. " "Example: --override resotocore.api.web_hosts=localhost,some.domain resotocore.api.web_port=12345", ) parser.add_argument("--verbose", "-v", dest="verbose", default=False, action="store_true", help="Enable verbose logging.") parser.add_argument( # No default here on purpose: it can be reconfigured! "--debug", default=None, action="store_true", help="Enable debug mode. If not defined use configuration.") parser.add_argument( # No default here on purpose: it can be reconfigured! "--ui-path", type=is_dir("can not parse --ui-dir"), help="Path to the UI files. If not defined use configuration..", ) parser.add_argument("--analytics-opt-out", default=None, action="store_true", help=argparse.SUPPRESS) parser.add_argument("--resotoeventlog-uri", dest="resotoeventlog_uri", default=None, help="URI to the resotoeventlog server.") parsed: Namespace = parser.parse_args(args if args else []) if parsed.version: # print here on purpose, since logging is not set up yet. print(f"resotocore {version()}") sys.exit(0) return parsed
def main() -> None: setup_logger("resotometrics") resotolib.proc.parent_pid = os.getpid() add_event_listener(EventType.SHUTDOWN, shutdown) arg_parser = ArgumentParser(description="resoto metrics exporter", env_args_prefix="RESOTOMETRICS_") add_args(arg_parser) Config.add_args(arg_parser) resotocore_add_args(arg_parser) logging_add_args(arg_parser) jwt_add_args(arg_parser) TLSData.add_args(arg_parser) arg_parser.parse_args() try: wait_for_resotocore(resotocore.http_uri) except TimeoutError as e: log.fatal(f"Failed to connect to resotocore: {e}") sys.exit(1) tls_data = None if resotocore.is_secure: tls_data = TLSData( common_name=ArgumentParser.args.subscriber_id, resotocore_uri=resotocore.http_uri, ) tls_data.start() config = Config( ArgumentParser.args.subscriber_id, resotocore_uri=resotocore.http_uri, tls_data=tls_data, ) config.add_config(ResotoMetricsConfig) config.load_config() resotolib.proc.initializer() metrics = Metrics() graph_collector = GraphCollector(metrics) REGISTRY.register(graph_collector) resotocore_graph = Config.resotometrics.graph graph_uri = f"{resotocore.http_uri}/graph/{resotocore_graph}" search_uri = f"{graph_uri}/search/aggregate?section=reported" message_processor = partial(core_actions_processor, metrics, search_uri, tls_data) core_actions = CoreActions( identifier=ArgumentParser.args.subscriber_id, resotocore_uri=resotocore.http_uri, resotocore_ws_uri=resotocore.ws_uri, actions={ "generate_metrics": { "timeout": Config.resotometrics.timeout, "wait_for_completion": True, }, }, message_processor=message_processor, tls_data=tls_data, ) web_server_args = {} if tls_data: web_server_args = { "ssl_cert": tls_data.cert_path, "ssl_key": tls_data.key_path, } web_server = WebServer( WebApp(mountpoint=Config.resotometrics.web_path), web_host=Config.resotometrics.web_host, web_port=Config.resotometrics.web_port, **web_server_args, ) web_server.daemon = True web_server.start() core_actions.start() shutdown_event.wait() web_server.shutdown() core_actions.shutdown() resotolib.proc.kill_children(resotolib.proc.SIGTERM, ensure_death=True) log.info("Shutdown complete") sys.exit(0)