moonraker: Add configparser support
Rather than receive its configuration from Klippy, moonraker will receive its configuration from a config file. By default this file is located at ~/moonraker.conf. Signed-off-by: Eric Callahan <arksine.code@gmail.com>
This commit is contained in:
parent
e2850ee77e
commit
7a94fb3a6b
|
@ -0,0 +1,89 @@
|
||||||
|
# Configuration Helper
|
||||||
|
#
|
||||||
|
# Copyright (C) 2020 Eric Callahan <arksine.code@gmail.com>
|
||||||
|
#
|
||||||
|
# This file may be distributed under the terms of the GNU GPLv3 license
|
||||||
|
import configparser
|
||||||
|
import os
|
||||||
|
import logging
|
||||||
|
|
||||||
|
class ConfigError(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class Sentinel:
|
||||||
|
pass
|
||||||
|
|
||||||
|
class ConfigHelper:
|
||||||
|
error = ConfigError
|
||||||
|
def __init__(self, server, config, section):
|
||||||
|
self.server = server
|
||||||
|
self.config = config
|
||||||
|
self.section = section
|
||||||
|
self.sections = config.sections
|
||||||
|
self.has_section = config.has_section
|
||||||
|
|
||||||
|
def get_server(self):
|
||||||
|
return self.server
|
||||||
|
|
||||||
|
def __getitem__(self, key):
|
||||||
|
return self.getsection(key)
|
||||||
|
|
||||||
|
def __contains__(self, key):
|
||||||
|
return key in self.config
|
||||||
|
|
||||||
|
def getsection(self, section):
|
||||||
|
if section not in self.config:
|
||||||
|
raise ConfigError(f"No section [{section}] in config")
|
||||||
|
return ConfigHelper(self.server, self.config, section)
|
||||||
|
|
||||||
|
def _get_option(self, func, option, default):
|
||||||
|
try:
|
||||||
|
val = func(option, default)
|
||||||
|
except Exception:
|
||||||
|
raise ConfigError(
|
||||||
|
f"Error parsing option ({option}) from "
|
||||||
|
f"section [{self.section}]")
|
||||||
|
if val == Sentinel:
|
||||||
|
raise ConfigError(
|
||||||
|
f"No option found ({option}) in section [{self.section}]")
|
||||||
|
return val
|
||||||
|
|
||||||
|
def get(self, option, default=Sentinel):
|
||||||
|
return self._get_option(
|
||||||
|
self.config[self.section].get, option, default)
|
||||||
|
|
||||||
|
def getint(self, option, default=Sentinel):
|
||||||
|
return self._get_option(
|
||||||
|
self.config[self.section].getint, option, default)
|
||||||
|
|
||||||
|
def getboolean(self, option, default=Sentinel):
|
||||||
|
return self._get_option(
|
||||||
|
self.config[self.section].getboolean, option, default)
|
||||||
|
|
||||||
|
def getfloat(self, option, default=Sentinel):
|
||||||
|
return self._get_item(
|
||||||
|
self.config[self.section].getfloat, option, default)
|
||||||
|
|
||||||
|
def get_configuration(server, cmd_line_args):
|
||||||
|
cfg_file_path = os.path.normpath(os.path.expanduser(
|
||||||
|
cmd_line_args.configfile))
|
||||||
|
if not os.path.isfile(cfg_file_path):
|
||||||
|
raise ConfigError(f"Configuration File Not Found: '{cfg_file_path}''")
|
||||||
|
config = configparser.ConfigParser(interpolation=None)
|
||||||
|
try:
|
||||||
|
config.read(cfg_file_path)
|
||||||
|
except Exception:
|
||||||
|
raise ConfigError(f"Error Reading Config: '{cfg_file_path}'") from None
|
||||||
|
|
||||||
|
try:
|
||||||
|
server_cfg = config['server']
|
||||||
|
except KeyError:
|
||||||
|
raise ConfigError("No section [server] in config")
|
||||||
|
|
||||||
|
if server_cfg.get('enable_debug_logging', True):
|
||||||
|
logging.getLogger().setLevel(logging.DEBUG)
|
||||||
|
|
||||||
|
config['cmd_args'] = {
|
||||||
|
'logfile': cmd_line_args.logfile,
|
||||||
|
'socketfile': cmd_line_args.socketfile}
|
||||||
|
return ConfigHelper(server, config, 'server')
|
|
@ -14,6 +14,7 @@ import json
|
||||||
import errno
|
import errno
|
||||||
import tornado
|
import tornado
|
||||||
import tornado.netutil
|
import tornado.netutil
|
||||||
|
import confighelper
|
||||||
from tornado import gen
|
from tornado import gen
|
||||||
from tornado.ioloop import IOLoop, PeriodicCallback
|
from tornado.ioloop import IOLoop, PeriodicCallback
|
||||||
from tornado.util import TimeoutError
|
from tornado.util import TimeoutError
|
||||||
|
@ -33,14 +34,16 @@ class Sentinel:
|
||||||
class Server:
|
class Server:
|
||||||
error = ServerError
|
error = ServerError
|
||||||
def __init__(self, args):
|
def __init__(self, args):
|
||||||
self.host = args.address
|
config = confighelper.get_configuration(self, args)
|
||||||
self.port = args.port
|
self.host = config.get('host', "0.0.0.0")
|
||||||
|
self.port = config.getint('port', 7125)
|
||||||
|
|
||||||
# Event initialization
|
# Event initialization
|
||||||
self.events = {}
|
self.events = {}
|
||||||
|
|
||||||
# Klippy Connection Handling
|
# Klippy Connection Handling
|
||||||
socketfile = os.path.normpath(os.path.expanduser(args.socketfile))
|
socketfile = config['cmd_args'].get('socketfile', "/tmp/moonraker")
|
||||||
|
socketfile = os.path.normpath(os.path.expanduser(socketfile))
|
||||||
self.klippy_server_sock = tornado.netutil.bind_unix_socket(
|
self.klippy_server_sock = tornado.netutil.bind_unix_socket(
|
||||||
socketfile, backlog=1)
|
socketfile, backlog=1)
|
||||||
self.remove_server_sock = tornado.netutil.add_accept_handler(
|
self.remove_server_sock = tornado.netutil.add_accept_handler(
|
||||||
|
@ -48,23 +51,17 @@ class Server:
|
||||||
self.klippy_sock = None
|
self.klippy_sock = None
|
||||||
self.is_klippy_connected = False
|
self.is_klippy_connected = False
|
||||||
self.is_klippy_ready = False
|
self.is_klippy_ready = False
|
||||||
self.server_configured = False
|
self.moonraker_available = False
|
||||||
self.partial_data = b""
|
self.partial_data = b""
|
||||||
|
|
||||||
# Server/IOLoop
|
# Server/IOLoop
|
||||||
self.server_running = False
|
self.server_running = False
|
||||||
self.moonraker_app = app = MoonrakerApp(self, args)
|
self.moonraker_app = app = MoonrakerApp(config)
|
||||||
self.io_loop = IOLoop.current()
|
|
||||||
self.init_cb = PeriodicCallback(self._initialize, INIT_MS)
|
|
||||||
|
|
||||||
# Plugin initialization
|
|
||||||
self.plugins = {}
|
|
||||||
self.register_endpoint = app.register_local_handler
|
self.register_endpoint = app.register_local_handler
|
||||||
self.register_static_file_handler = app.register_static_file_handler
|
self.register_static_file_handler = app.register_static_file_handler
|
||||||
self.register_upload_handler = app.register_upload_handler
|
self.register_upload_handler = app.register_upload_handler
|
||||||
|
self.io_loop = IOLoop.current()
|
||||||
for plugin in CORE_PLUGINS:
|
self.init_cb = PeriodicCallback(self._initialize, INIT_MS)
|
||||||
self.load_plugin(plugin)
|
|
||||||
|
|
||||||
# Setup remote methods accessable to Klippy. Note that all
|
# Setup remote methods accessable to Klippy. Note that all
|
||||||
# registered remote methods should be of the notification type,
|
# registered remote methods should be of the notification type,
|
||||||
|
@ -80,6 +77,10 @@ class Server:
|
||||||
self.register_remote_method(
|
self.register_remote_method(
|
||||||
'process_status_update', self._process_status_update)
|
'process_status_update', self._process_status_update)
|
||||||
|
|
||||||
|
# Plugin initialization
|
||||||
|
self.plugins = {}
|
||||||
|
self._load_plugins(config)
|
||||||
|
|
||||||
def start(self):
|
def start(self):
|
||||||
logging.info(
|
logging.info(
|
||||||
"Starting Moonraker on (%s, %d)" %
|
"Starting Moonraker on (%s, %d)" %
|
||||||
|
@ -88,7 +89,18 @@ class Server:
|
||||||
self.server_running = True
|
self.server_running = True
|
||||||
|
|
||||||
# ***** Plugin Management *****
|
# ***** Plugin Management *****
|
||||||
def load_plugin(self, plugin_name, default=Sentinel):
|
def _load_plugins(self, config):
|
||||||
|
# load core plugins
|
||||||
|
for plugin in CORE_PLUGINS:
|
||||||
|
self.load_plugin(config, plugin)
|
||||||
|
|
||||||
|
# check for optional plugins
|
||||||
|
opt_sections = set(config.sections()) - \
|
||||||
|
set(['server', 'authorization', 'cmd_args'])
|
||||||
|
for section in opt_sections:
|
||||||
|
self.load_plugin(config[section], section, None)
|
||||||
|
|
||||||
|
def load_plugin(self, config, plugin_name, default=Sentinel):
|
||||||
if plugin_name in self.plugins:
|
if plugin_name in self.plugins:
|
||||||
return self.plugins[plugin_name]
|
return self.plugins[plugin_name]
|
||||||
# Make sure plugin exists
|
# Make sure plugin exists
|
||||||
|
@ -103,7 +115,7 @@ class Server:
|
||||||
module = importlib.import_module("plugins." + plugin_name)
|
module = importlib.import_module("plugins." + plugin_name)
|
||||||
try:
|
try:
|
||||||
load_func = getattr(module, "load_plugin")
|
load_func = getattr(module, "load_plugin")
|
||||||
plugin = load_func(self)
|
plugin = load_func(config)
|
||||||
except Exception:
|
except Exception:
|
||||||
msg = "Unable to load plugin (%s)" % (plugin_name)
|
msg = "Unable to load plugin (%s)" % (plugin_name)
|
||||||
logging.info(msg)
|
logging.info(msg)
|
||||||
|
@ -215,13 +227,14 @@ class Server:
|
||||||
|
|
||||||
async def _initialize(self):
|
async def _initialize(self):
|
||||||
await self._request_endpoints()
|
await self._request_endpoints()
|
||||||
if not self.server_configured:
|
if not self.moonraker_available:
|
||||||
await self._request_config()
|
await self._check_available()
|
||||||
if not self.is_klippy_ready:
|
elif not self.is_klippy_ready:
|
||||||
await self._request_ready()
|
await self._check_ready()
|
||||||
if self.is_klippy_ready and self.server_configured:
|
else:
|
||||||
# Make sure we have all registered endpoints
|
# Moonraker is enabled in the Klippy module
|
||||||
await self._request_endpoints()
|
# and Klippy is ready. We can stop the init
|
||||||
|
# procedure.
|
||||||
self.init_cb.stop()
|
self.init_cb.stop()
|
||||||
|
|
||||||
async def _request_endpoints(self):
|
async def _request_endpoints(self):
|
||||||
|
@ -236,20 +249,21 @@ class Server:
|
||||||
self.moonraker_app.register_static_file_handler(
|
self.moonraker_app.register_static_file_handler(
|
||||||
sp['resource_id'], sp['file_path'])
|
sp['resource_id'], sp['file_path'])
|
||||||
|
|
||||||
async def _request_config(self):
|
async def _check_available(self):
|
||||||
request = self.make_request(
|
request = self.make_request(
|
||||||
"moonraker/get_configuration", "GET", {})
|
"moonraker/check_available", "GET", {})
|
||||||
result = await request.wait()
|
result = await request.wait()
|
||||||
if not isinstance(result, ServerError):
|
if not isinstance(result, ServerError):
|
||||||
self._load_config(result)
|
self.send_event("server:moonraker_available", result)
|
||||||
self.server_configured = True
|
self.moonraker_available = True
|
||||||
else:
|
else:
|
||||||
logging.info(
|
logging.info(
|
||||||
"Error receiving configuration. This indicates a "
|
"\nCheck for moonraker availability has failed. This "
|
||||||
"potential configuration issue in printer.cfg. Please check "
|
"indicates that the [moonraker] section has not been added to "
|
||||||
"klippy.log for more information")
|
"printer.cfg, or that Klippy has experienced an error "
|
||||||
|
"parsing its configuraton. Check klippy.log for more info.")
|
||||||
|
|
||||||
async def _request_ready(self):
|
async def _check_ready(self):
|
||||||
request = self.make_request("info", "GET", {})
|
request = self.make_request("info", "GET", {})
|
||||||
result = await request.wait()
|
result = await request.wait()
|
||||||
if not isinstance(result, ServerError):
|
if not isinstance(result, ServerError):
|
||||||
|
@ -265,33 +279,6 @@ class Server:
|
||||||
"may have experienced an error during startup. Please check "
|
"may have experienced an error during startup. Please check "
|
||||||
"klippy.log for more information")
|
"klippy.log for more information")
|
||||||
|
|
||||||
def _load_config(self, config):
|
|
||||||
self.moonraker_app.load_config(config)
|
|
||||||
# load config for core plugins
|
|
||||||
for plugin_name in CORE_PLUGINS:
|
|
||||||
plugin = self.plugins[plugin_name]
|
|
||||||
if hasattr(plugin, "load_config"):
|
|
||||||
plugin.load_config(config)
|
|
||||||
# Load and apply optional plugin Configuration
|
|
||||||
plugin_cfgs = {name[7:]: cfg for name, cfg in config.items()
|
|
||||||
if name.startswith("plugin_")}
|
|
||||||
for name, cfg in plugin_cfgs.items():
|
|
||||||
plugin = self.plugins.get(name)
|
|
||||||
if plugin is None:
|
|
||||||
plugin = self.load_plugin(name, None)
|
|
||||||
if hasattr(plugin, "load_config"):
|
|
||||||
plugin.load_config(cfg)
|
|
||||||
# Remove plugins that are loaded but no longer configured
|
|
||||||
valid_plugins = CORE_PLUGINS + list(plugin_cfgs.keys())
|
|
||||||
self.io_loop.spawn_callback(self._prune_plugins, valid_plugins)
|
|
||||||
|
|
||||||
async def _prune_plugins(self, valid_plugins):
|
|
||||||
for name, plugin in self.plugins.items():
|
|
||||||
if name not in valid_plugins:
|
|
||||||
if hasattr(plugin, "close"):
|
|
||||||
await plugin.close()
|
|
||||||
self.plugins.pop(name, None)
|
|
||||||
|
|
||||||
def _handle_klippy_response(self, request_id, response):
|
def _handle_klippy_response(self, request_id, response):
|
||||||
req = self.pending_requests.pop(request_id, None)
|
req = self.pending_requests.pop(request_id, None)
|
||||||
if req is not None:
|
if req is not None:
|
||||||
|
@ -345,7 +332,7 @@ class Server:
|
||||||
|
|
||||||
def close_client_sock(self):
|
def close_client_sock(self):
|
||||||
self.is_klippy_ready = False
|
self.is_klippy_ready = False
|
||||||
self.server_configured = False
|
self.moonraker_available = False
|
||||||
self.init_cb.stop()
|
self.init_cb.stop()
|
||||||
for request in self.pending_requests.values():
|
for request in self.pending_requests.values():
|
||||||
request.notify(ServerError("Klippy Disconnected", 503))
|
request.notify(ServerError("Klippy Disconnected", 503))
|
||||||
|
@ -407,23 +394,15 @@ def main():
|
||||||
parser = argparse.ArgumentParser(
|
parser = argparse.ArgumentParser(
|
||||||
description="Moonraker - Klipper API Server")
|
description="Moonraker - Klipper API Server")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"-a", "--address", default='0.0.0.0', metavar='<address>',
|
"-c", "--configfile", default="~/moonraker.conf",
|
||||||
help="host name or ip to bind to the Web Server")
|
metavar='<configfile>',
|
||||||
parser.add_argument(
|
help="Location of moonraker configuration file")
|
||||||
"-p", "--port", type=int, default=7125, metavar='<port>',
|
|
||||||
help="port the Web Server will listen on")
|
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"-s", "--socketfile", default="/tmp/moonraker", metavar='<socketfile>',
|
"-s", "--socketfile", default="/tmp/moonraker", metavar='<socketfile>',
|
||||||
help="file name and location for the Unix Domain Socket")
|
help="file name and location for the Unix Domain Socket")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"-l", "--logfile", default="/tmp/moonraker.log", metavar='<logfile>',
|
"-l", "--logfile", default="/tmp/moonraker.log", metavar='<logfile>',
|
||||||
help="log file name and location")
|
help="log file name and location")
|
||||||
parser.add_argument(
|
|
||||||
"-k", "--apikey", default="~/.moonraker_api_key",
|
|
||||||
metavar='<apikeyfile>', help="API Key file location")
|
|
||||||
parser.add_argument(
|
|
||||||
"-d", "--debug", action='store_true',
|
|
||||||
help="Enable Debug Logging")
|
|
||||||
cmd_line_args = parser.parse_args()
|
cmd_line_args = parser.parse_args()
|
||||||
|
|
||||||
# Setup Logging
|
# Setup Logging
|
||||||
|
@ -433,10 +412,7 @@ def main():
|
||||||
file_hdlr = MoonrakerLoggingHandler(
|
file_hdlr = MoonrakerLoggingHandler(
|
||||||
log_file, when='midnight', backupCount=2)
|
log_file, when='midnight', backupCount=2)
|
||||||
root_logger.addHandler(file_hdlr)
|
root_logger.addHandler(file_hdlr)
|
||||||
if cmd_line_args.debug:
|
root_logger.setLevel(logging.INFO)
|
||||||
root_logger.setLevel(logging.DEBUG)
|
|
||||||
else:
|
|
||||||
root_logger.setLevel(logging.INFO)
|
|
||||||
formatter = logging.Formatter(
|
formatter = logging.Formatter(
|
||||||
'%(asctime)s [%(filename)s:%(funcName)s()] - %(message)s')
|
'%(asctime)s [%(filename)s:%(funcName)s()] - %(message)s')
|
||||||
file_hdlr.setFormatter(formatter)
|
file_hdlr.setFormatter(formatter)
|
||||||
|
@ -454,7 +430,7 @@ def main():
|
||||||
server = Server(cmd_line_args)
|
server = Server(cmd_line_args)
|
||||||
except Exception:
|
except Exception:
|
||||||
logging.exception("Moonraker Error")
|
logging.exception("Moonraker Error")
|
||||||
return
|
exit(1)
|
||||||
try:
|
try:
|
||||||
server.start()
|
server.start()
|
||||||
io_loop.start()
|
io_loop.start()
|
||||||
|
|
Loading…
Reference in New Issue