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:
Arksine 2020-08-05 20:44:21 -04:00
parent e2850ee77e
commit 7a94fb3a6b
2 changed files with 139 additions and 74 deletions

89
moonraker/confighelper.py Normal file
View File

@ -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')

View File

@ -14,6 +14,7 @@ import json
import errno
import tornado
import tornado.netutil
import confighelper
from tornado import gen
from tornado.ioloop import IOLoop, PeriodicCallback
from tornado.util import TimeoutError
@ -33,14 +34,16 @@ class Sentinel:
class Server:
error = ServerError
def __init__(self, args):
self.host = args.address
self.port = args.port
config = confighelper.get_configuration(self, args)
self.host = config.get('host', "0.0.0.0")
self.port = config.getint('port', 7125)
# Event initialization
self.events = {}
# 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(
socketfile, backlog=1)
self.remove_server_sock = tornado.netutil.add_accept_handler(
@ -48,23 +51,17 @@ class Server:
self.klippy_sock = None
self.is_klippy_connected = False
self.is_klippy_ready = False
self.server_configured = False
self.moonraker_available = False
self.partial_data = b""
# Server/IOLoop
self.server_running = False
self.moonraker_app = app = MoonrakerApp(self, args)
self.io_loop = IOLoop.current()
self.init_cb = PeriodicCallback(self._initialize, INIT_MS)
# Plugin initialization
self.plugins = {}
self.moonraker_app = app = MoonrakerApp(config)
self.register_endpoint = app.register_local_handler
self.register_static_file_handler = app.register_static_file_handler
self.register_upload_handler = app.register_upload_handler
for plugin in CORE_PLUGINS:
self.load_plugin(plugin)
self.io_loop = IOLoop.current()
self.init_cb = PeriodicCallback(self._initialize, INIT_MS)
# Setup remote methods accessable to Klippy. Note that all
# registered remote methods should be of the notification type,
@ -80,6 +77,10 @@ class Server:
self.register_remote_method(
'process_status_update', self._process_status_update)
# Plugin initialization
self.plugins = {}
self._load_plugins(config)
def start(self):
logging.info(
"Starting Moonraker on (%s, %d)" %
@ -88,7 +89,18 @@ class Server:
self.server_running = True
# ***** 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:
return self.plugins[plugin_name]
# Make sure plugin exists
@ -103,7 +115,7 @@ class Server:
module = importlib.import_module("plugins." + plugin_name)
try:
load_func = getattr(module, "load_plugin")
plugin = load_func(self)
plugin = load_func(config)
except Exception:
msg = "Unable to load plugin (%s)" % (plugin_name)
logging.info(msg)
@ -215,13 +227,14 @@ class Server:
async def _initialize(self):
await self._request_endpoints()
if not self.server_configured:
await self._request_config()
if not self.is_klippy_ready:
await self._request_ready()
if self.is_klippy_ready and self.server_configured:
# Make sure we have all registered endpoints
await self._request_endpoints()
if not self.moonraker_available:
await self._check_available()
elif not self.is_klippy_ready:
await self._check_ready()
else:
# Moonraker is enabled in the Klippy module
# and Klippy is ready. We can stop the init
# procedure.
self.init_cb.stop()
async def _request_endpoints(self):
@ -236,20 +249,21 @@ class Server:
self.moonraker_app.register_static_file_handler(
sp['resource_id'], sp['file_path'])
async def _request_config(self):
async def _check_available(self):
request = self.make_request(
"moonraker/get_configuration", "GET", {})
"moonraker/check_available", "GET", {})
result = await request.wait()
if not isinstance(result, ServerError):
self._load_config(result)
self.server_configured = True
self.send_event("server:moonraker_available", result)
self.moonraker_available = True
else:
logging.info(
"Error receiving configuration. This indicates a "
"potential configuration issue in printer.cfg. Please check "
"klippy.log for more information")
"\nCheck for moonraker availability has failed. This "
"indicates that the [moonraker] section has not been added to "
"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", {})
result = await request.wait()
if not isinstance(result, ServerError):
@ -265,33 +279,6 @@ class Server:
"may have experienced an error during startup. Please check "
"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):
req = self.pending_requests.pop(request_id, None)
if req is not None:
@ -345,7 +332,7 @@ class Server:
def close_client_sock(self):
self.is_klippy_ready = False
self.server_configured = False
self.moonraker_available = False
self.init_cb.stop()
for request in self.pending_requests.values():
request.notify(ServerError("Klippy Disconnected", 503))
@ -407,23 +394,15 @@ def main():
parser = argparse.ArgumentParser(
description="Moonraker - Klipper API Server")
parser.add_argument(
"-a", "--address", default='0.0.0.0', metavar='<address>',
help="host name or ip to bind to the Web Server")
parser.add_argument(
"-p", "--port", type=int, default=7125, metavar='<port>',
help="port the Web Server will listen on")
"-c", "--configfile", default="~/moonraker.conf",
metavar='<configfile>',
help="Location of moonraker configuration file")
parser.add_argument(
"-s", "--socketfile", default="/tmp/moonraker", metavar='<socketfile>',
help="file name and location for the Unix Domain Socket")
parser.add_argument(
"-l", "--logfile", default="/tmp/moonraker.log", metavar='<logfile>',
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()
# Setup Logging
@ -433,9 +412,6 @@ def main():
file_hdlr = MoonrakerLoggingHandler(
log_file, when='midnight', backupCount=2)
root_logger.addHandler(file_hdlr)
if cmd_line_args.debug:
root_logger.setLevel(logging.DEBUG)
else:
root_logger.setLevel(logging.INFO)
formatter = logging.Formatter(
'%(asctime)s [%(filename)s:%(funcName)s()] - %(message)s')
@ -454,7 +430,7 @@ def main():
server = Server(cmd_line_args)
except Exception:
logging.exception("Moonraker Error")
return
exit(1)
try:
server.start()
io_loop.start()