Convert config to oslo_config

This patch is the initial conversion of the custom config
and config file yaml format to oslo_config's configuration mechanism.

The resulting config format is now an ini type file.

The default location is ~/.config/aprsd/aprsd.conf

This is a backwards incompatible change.  You will have to rebuild
the config file and edit it.

Also any aprsd plugins can now define config options in code and
add an setup.cfg entry_point definition
oslo_config.opts  =
  foo.conf = foo.conf:list_opts
This commit is contained in:
Hemna 2022-12-24 13:53:06 -05:00
parent ce3b29f990
commit e13ca0061a
29 changed files with 496 additions and 430 deletions

View File

@ -1,9 +1,37 @@
CHANGES CHANGES
======= =======
* Added rain formatting unit tests to WeatherPacket
* Fix Rain reporting in WeatherPacket send
* Removed Packet.send()
* Removed watchlist plugins
* Fix PluginManager.get\_plugins
* Cleaned up PluginManager
* Cleaned up PluginManager
* Update routing for weatherpacket
* Fix some WeatherPacket formatting
* Fix pep8 violation
* Add packet filtering for aprsd listen
* Added WeatherPacket encoding
* Updated webchat and listen for queue based RX
* reworked collecting and reporting stats
* Removed unused threading code
* Change RX packet processing to enqueu
* Make tracking objectstores work w/o initializing
* Cleaned up packet transmit class attributes
* Fix packets timestamp to int
* More messaging -> packets cleanup
* Cleaned out all references to messaging
* Added contructing a GPSPacket for sending
* cleanup webchat
* Reworked all packet processing
* Updated plugins and plugin interfaces for Packet
* Started using dataclasses to describe packets
v2.6.1 v2.6.1
------ ------
* v2.6.1
* Fixed position report for webchat beacon * Fixed position report for webchat beacon
* Try and fix broken 32bit qemu builds on 64bit system * Try and fix broken 32bit qemu builds on 64bit system
* Add unit tests for webchat * Add unit tests for webchat

View File

@ -21,6 +21,8 @@
# python included libs # python included libs
import datetime import datetime
from importlib.metadata import entry_points
from importlib.metadata import version as metadata_version
import logging import logging
import os import os
import signal import signal
@ -29,12 +31,11 @@ import time
import click import click
import click_completion import click_completion
from oslo_config import cfg, generator
# local imports here # local imports here
import aprsd import aprsd
from aprsd import cli_helper from aprsd import cli_helper, packets, stats, threads, utils
from aprsd import config as aprsd_config
from aprsd import packets, stats, threads, utils
# setup the global logger # setup the global logger
@ -111,8 +112,32 @@ def check_version(ctx):
@cli.command() @cli.command()
@click.pass_context @click.pass_context
def sample_config(ctx): def sample_config(ctx):
"""This dumps the config to stdout.""" """Generate a sample Config file from aprsd and all installed plugins."""
click.echo(aprsd_config.dump_default_cfg())
def get_namespaces():
args = []
selected = entry_points(group="oslo.config.opts")
for entry in selected:
if "aprsd" in entry.name:
args.append("--namespace")
args.append(entry.name)
return args
args = get_namespaces()
config_version = metadata_version("oslo.config")
logging.basicConfig(level=logging.WARN)
conf = cfg.ConfigOpts()
generator.register_cli_opts(conf)
try:
conf(args, version=config_version)
except cfg.RequiredOptError:
conf.print_help()
if not sys.argv[1:]:
raise SystemExit
raise
generator.generate(conf)
@cli.command() @cli.command()

View File

@ -1,13 +1,22 @@
from functools import update_wrapper from functools import update_wrapper
from pathlib import Path
import typing as t import typing as t
import click import click
from oslo_config import cfg
from aprsd import config as aprsd_config import aprsd
from aprsd.logging import log from aprsd.logging import log
from aprsd.utils import trace from aprsd.utils import trace
CONF = cfg.CONF
home = str(Path.home())
DEFAULT_CONFIG_DIR = f"{home}/.config/aprsd/"
DEFAULT_SAVE_FILE = f"{home}/.config/aprsd/aprsd.p"
DEFAULT_CONFIG_FILE = f"{home}/.config/aprsd/aprsd.conf"
F = t.TypeVar("F", bound=t.Callable[..., t.Any]) F = t.TypeVar("F", bound=t.Callable[..., t.Any])
common_options = [ common_options = [
@ -27,7 +36,7 @@ common_options = [
"--config", "--config",
"config_file", "config_file",
show_default=True, show_default=True,
default=aprsd_config.DEFAULT_CONFIG_FILE, default=DEFAULT_CONFIG_FILE,
help="The aprsd config file to use for options.", help="The aprsd config file to use for options.",
), ),
click.option( click.option(
@ -51,16 +60,22 @@ def process_standard_options(f: F) -> F:
def new_func(*args, **kwargs): def new_func(*args, **kwargs):
ctx = args[0] ctx = args[0]
ctx.ensure_object(dict) ctx.ensure_object(dict)
if kwargs["config_file"]:
default_config_files = [kwargs["config_file"]]
else:
default_config_files = None
CONF(
[], project="aprsd", version=aprsd.__version__,
default_config_files=default_config_files,
)
ctx.obj["loglevel"] = kwargs["loglevel"] ctx.obj["loglevel"] = kwargs["loglevel"]
ctx.obj["config_file"] = kwargs["config_file"] ctx.obj["config_file"] = kwargs["config_file"]
ctx.obj["quiet"] = kwargs["quiet"] ctx.obj["quiet"] = kwargs["quiet"]
ctx.obj["config"] = aprsd_config.parse_config(kwargs["config_file"])
log.setup_logging( log.setup_logging(
ctx.obj["config"],
ctx.obj["loglevel"], ctx.obj["loglevel"],
ctx.obj["quiet"], ctx.obj["quiet"],
) )
if ctx.obj["config"]["aprsd"].get("trace", False): if CONF.trace_enabled:
trace.setup_tracing(["method", "api"]) trace.setup_tracing(["method", "api"])
del kwargs["loglevel"] del kwargs["loglevel"]

View File

@ -4,14 +4,15 @@ import time
import aprslib import aprslib
from aprslib.exceptions import LoginError from aprslib.exceptions import LoginError
from oslo_config import cfg
from aprsd import config as aprsd_config
from aprsd import exception from aprsd import exception
from aprsd.clients import aprsis, kiss from aprsd.clients import aprsis, kiss
from aprsd.packets import core, packet_list from aprsd.packets import core, packet_list
from aprsd.utils import trace from aprsd.utils import trace
CONF = cfg.CONF
LOG = logging.getLogger("APRSD") LOG = logging.getLogger("APRSD")
TRANSPORT_APRSIS = "aprsis" TRANSPORT_APRSIS = "aprsis"
TRANSPORT_TCPKISS = "tcpkiss" TRANSPORT_TCPKISS = "tcpkiss"
@ -28,7 +29,6 @@ class Client:
_instance = None _instance = None
_client = None _client = None
config = None
connected = False connected = False
server_string = None server_string = None
@ -41,11 +41,6 @@ class Client:
# Put any initialization here. # Put any initialization here.
return cls._instance return cls._instance
def __init__(self, config=None):
"""Initialize the object instance."""
if config:
self.config = config
def set_filter(self, filter): def set_filter(self, filter):
self.filter = filter self.filter = filter
if self._client: if self._client:
@ -74,12 +69,12 @@ class Client:
@staticmethod @staticmethod
@abc.abstractmethod @abc.abstractmethod
def is_enabled(config): def is_enabled():
pass pass
@staticmethod @staticmethod
@abc.abstractmethod @abc.abstractmethod
def transport(config): def transport():
pass pass
@abc.abstractmethod @abc.abstractmethod
@ -90,26 +85,38 @@ class Client:
class APRSISClient(Client): class APRSISClient(Client):
@staticmethod @staticmethod
def is_enabled(config): def is_enabled():
# Defaults to True if the enabled flag is non existent # Defaults to True if the enabled flag is non existent
try: try:
return config["aprs"].get("enabled", True) return CONF.aprs_network.enabled
except KeyError: except KeyError:
return False return False
@staticmethod @staticmethod
def is_configured(config): def is_configured():
if APRSISClient.is_enabled(config): if APRSISClient.is_enabled():
# Ensure that the config vars are correctly set # Ensure that the config vars are correctly set
config.check_option("aprs.login") if not CONF.aprs_network.login:
config.check_option("aprs.password") LOG.error("Config aprs_network.login not set.")
config.check_option("aprs.host") raise exception.MissingConfigOptionException(
return True "aprs_network.login is not set.",
)
if not CONF.aprs_network.password:
LOG.error("Config aprs_network.password not set.")
raise exception.MissingConfigOptionException(
"aprs_network.password is not set.",
)
if not CONF.aprs_network.host:
LOG.error("Config aprs_network.host not set.")
raise exception.MissingConfigOptionException(
"aprs_network.host is not set.",
)
return True
return True return True
@staticmethod @staticmethod
def transport(config): def transport():
return TRANSPORT_APRSIS return TRANSPORT_APRSIS
def decode_packet(self, *args, **kwargs): def decode_packet(self, *args, **kwargs):
@ -118,10 +125,10 @@ class APRSISClient(Client):
@trace.trace @trace.trace
def setup_connection(self): def setup_connection(self):
user = self.config["aprs"]["login"] user = CONF.aprs_network.login
password = self.config["aprs"]["password"] password = CONF.aprs_network.password
host = self.config["aprs"].get("host", "rotate.aprs.net") host = CONF.aprs_network.host
port = self.config["aprs"].get("port", 14580) port = CONF.aprs_network.port
connected = False connected = False
backoff = 1 backoff = 1
aprs_client = None aprs_client = None
@ -151,45 +158,43 @@ class APRSISClient(Client):
class KISSClient(Client): class KISSClient(Client):
@staticmethod @staticmethod
def is_enabled(config): def is_enabled():
"""Return if tcp or serial KISS is enabled.""" """Return if tcp or serial KISS is enabled."""
if "kiss" not in config: if CONF.kiss_serial.enabled:
return False
if config.get("kiss.serial.enabled", default=False):
return True return True
if config.get("kiss.tcp.enabled", default=False): if CONF.kiss_tcp.enabled:
return True return True
return False return False
@staticmethod @staticmethod
def is_configured(config): def is_configured():
# Ensure that the config vars are correctly set # Ensure that the config vars are correctly set
if KISSClient.is_enabled(config): if KISSClient.is_enabled():
config.check_option( transport = KISSClient.transport()
"aprsd.callsign",
default_fail=aprsd_config.DEFAULT_CONFIG_DICT["aprsd"]["callsign"],
)
transport = KISSClient.transport(config)
if transport == TRANSPORT_SERIALKISS: if transport == TRANSPORT_SERIALKISS:
config.check_option("kiss.serial") if not CONF.kiss_serial.device:
config.check_option("kiss.serial.device") LOG.error("KISS serial enabled, but no device is set.")
raise exception.MissingConfigOptionException(
"kiss_serial.device is not set.",
)
elif transport == TRANSPORT_TCPKISS: elif transport == TRANSPORT_TCPKISS:
config.check_option("kiss.tcp") if not CONF.kiss_tcp.host:
config.check_option("kiss.tcp.host") LOG.error("KISS TCP enabled, but no host is set.")
config.check_option("kiss.tcp.port") raise exception.MissingConfigOptionException(
"kiss_tcp.host is not set.",
)
return True return True
return True return False
@staticmethod @staticmethod
def transport(config): def transport():
if config.get("kiss.serial.enabled", default=False): if CONF.kiss_serial.enabled:
return TRANSPORT_SERIALKISS return TRANSPORT_SERIALKISS
if config.get("kiss.tcp.enabled", default=False): if CONF.kiss_tcp.enabled:
return TRANSPORT_TCPKISS return TRANSPORT_TCPKISS
def decode_packet(self, *args, **kwargs): def decode_packet(self, *args, **kwargs):
@ -208,7 +213,7 @@ class KISSClient(Client):
@trace.trace @trace.trace
def setup_connection(self): def setup_connection(self):
client = kiss.KISS3Client(self.config) client = kiss.KISS3Client()
return client return client
@ -222,8 +227,7 @@ class ClientFactory:
# Put any initialization here. # Put any initialization here.
return cls._instance return cls._instance
def __init__(self, config): def __init__(self):
self.config = config
self._builders = {} self._builders = {}
def register(self, key, builder): def register(self, key, builder):
@ -231,23 +235,23 @@ class ClientFactory:
def create(self, key=None): def create(self, key=None):
if not key: if not key:
if APRSISClient.is_enabled(self.config): if APRSISClient.is_enabled():
key = TRANSPORT_APRSIS key = TRANSPORT_APRSIS
elif KISSClient.is_enabled(self.config): elif KISSClient.is_enabled():
key = KISSClient.transport(self.config) key = KISSClient.transport()
LOG.debug(f"GET client '{key}'") LOG.debug(f"GET client '{key}'")
builder = self._builders.get(key) builder = self._builders.get(key)
if not builder: if not builder:
raise ValueError(key) raise ValueError(key)
return builder(self.config) return builder()
def is_client_enabled(self): def is_client_enabled(self):
"""Make sure at least one client is enabled.""" """Make sure at least one client is enabled."""
enabled = False enabled = False
for key in self._builders.keys(): for key in self._builders.keys():
try: try:
enabled |= self._builders[key].is_enabled(self.config) enabled |= self._builders[key].is_enabled()
except KeyError: except KeyError:
pass pass
@ -257,7 +261,7 @@ class ClientFactory:
enabled = False enabled = False
for key in self._builders.keys(): for key in self._builders.keys():
try: try:
enabled |= self._builders[key].is_configured(self.config) enabled |= self._builders[key].is_configured()
except KeyError: except KeyError:
pass pass
except exception.MissingConfigOptionException as ex: except exception.MissingConfigOptionException as ex:
@ -270,11 +274,11 @@ class ClientFactory:
return enabled return enabled
@staticmethod @staticmethod
def setup(config): def setup():
"""Create and register all possible client objects.""" """Create and register all possible client objects."""
global factory global factory
factory = ClientFactory(config) factory = ClientFactory()
factory.register(TRANSPORT_APRSIS, APRSISClient) factory.register(TRANSPORT_APRSIS, APRSISClient)
factory.register(TRANSPORT_TCPKISS, KISSClient) factory.register(TRANSPORT_TCPKISS, KISSClient)
factory.register(TRANSPORT_SERIALKISS, KISSClient) factory.register(TRANSPORT_SERIALKISS, KISSClient)

View File

@ -10,11 +10,12 @@ import sys
import time import time
import click import click
from oslo_config import cfg
from rich.console import Console from rich.console import Console
# local imports here # local imports here
import aprsd import aprsd
from aprsd import cli_helper, client, packets, stats, threads, utils from aprsd import cli_helper, client, packets, stats, threads
from aprsd.aprsd import cli from aprsd.aprsd import cli
from aprsd.threads import rx from aprsd.threads import rx
@ -22,6 +23,7 @@ from aprsd.threads import rx
# setup the global logger # setup the global logger
# logging.basicConfig(level=logging.DEBUG) # level=10 # logging.basicConfig(level=logging.DEBUG) # level=10
LOG = logging.getLogger("APRSD") LOG = logging.getLogger("APRSD")
CONF = cfg.CONF
console = Console() console = Console()
@ -38,8 +40,8 @@ def signal_handler(sig, frame):
class APRSDListenThread(rx.APRSDRXThread): class APRSDListenThread(rx.APRSDRXThread):
def __init__(self, config, packet_queue, packet_filter=None): def __init__(self, packet_queue, packet_filter=None):
super().__init__(config, packet_queue) super().__init__(packet_queue)
self.packet_filter = packet_filter self.packet_filter = packet_filter
def process_packet(self, *args, **kwargs): def process_packet(self, *args, **kwargs):
@ -118,7 +120,6 @@ def listen(
""" """
signal.signal(signal.SIGINT, signal_handler) signal.signal(signal.SIGINT, signal_handler)
signal.signal(signal.SIGTERM, signal_handler) signal.signal(signal.SIGTERM, signal_handler)
config = ctx.obj["config"]
if not aprs_login: if not aprs_login:
click.echo(ctx.get_help()) click.echo(ctx.get_help())
@ -132,27 +133,19 @@ def listen(
ctx.fail("Must set --aprs-password or APRS_PASSWORD") ctx.fail("Must set --aprs-password or APRS_PASSWORD")
ctx.exit() ctx.exit()
config["aprs"]["login"] = aprs_login # CONF.aprs_network.login = aprs_login
config["aprs"]["password"] = aprs_password # config["aprs"]["password"] = aprs_password
LOG.info(f"APRSD Listen Started version: {aprsd.__version__}") LOG.info(f"APRSD Listen Started version: {aprsd.__version__}")
flat_config = utils.flatten_dict(config) CONF.log_opt_values(LOG, logging.DEBUG)
LOG.info("Using CONFIG values:")
for x in flat_config:
if "password" in x or "aprsd.web.users.admin" in x:
LOG.info(f"{x} = XXXXXXXXXXXXXXXXXXX")
else:
LOG.info(f"{x} = {flat_config[x]}")
stats.APRSDStats(config)
# Try and load saved MsgTrack list # Try and load saved MsgTrack list
LOG.debug("Loading saved MsgTrack object.") LOG.debug("Loading saved MsgTrack object.")
# Initialize the client factory and create # Initialize the client factory and create
# The correct client object ready for use # The correct client object ready for use
client.ClientFactory.setup(config) client.ClientFactory.setup()
# Make sure we have 1 client transport enabled # Make sure we have 1 client transport enabled
if not client.factory.is_client_enabled(): if not client.factory.is_client_enabled():
LOG.error("No Clients are enabled in config.") LOG.error("No Clients are enabled in config.")
@ -166,12 +159,11 @@ def listen(
LOG.debug(f"Filter by '{filter}'") LOG.debug(f"Filter by '{filter}'")
aprs_client.set_filter(filter) aprs_client.set_filter(filter)
keepalive = threads.KeepAliveThread(config=config) keepalive = threads.KeepAliveThread()
keepalive.start() keepalive.start()
LOG.debug("Create APRSDListenThread") LOG.debug("Create APRSDListenThread")
listen_thread = APRSDListenThread( listen_thread = APRSDListenThread(
config=config,
packet_queue=threads.packet_queue, packet_queue=threads.packet_queue,
packet_filter=packet_filter, packet_filter=packet_filter,
) )

View File

@ -3,16 +3,16 @@ import signal
import sys import sys
import click import click
from oslo_config import cfg
import aprsd import aprsd
from aprsd import (
cli_helper, client, flask, packets, plugin, stats, threads, utils,
)
from aprsd import aprsd as aprsd_main from aprsd import aprsd as aprsd_main
from aprsd import cli_helper, client, flask, packets, plugin, threads, utils
from aprsd.aprsd import cli from aprsd.aprsd import cli
from aprsd.threads import rx from aprsd.threads import rx
CONF = cfg.CONF
LOG = logging.getLogger("APRSD") LOG = logging.getLogger("APRSD")
@ -32,10 +32,8 @@ LOG = logging.getLogger("APRSD")
@cli_helper.process_standard_options @cli_helper.process_standard_options
def server(ctx, flush): def server(ctx, flush):
"""Start the aprsd server gateway process.""" """Start the aprsd server gateway process."""
ctx.obj["config_file"]
loglevel = ctx.obj["loglevel"] loglevel = ctx.obj["loglevel"]
quiet = ctx.obj["quiet"] quiet = ctx.obj["quiet"]
config = ctx.obj["config"]
signal.signal(signal.SIGINT, aprsd_main.signal_handler) signal.signal(signal.SIGINT, aprsd_main.signal_handler)
signal.signal(signal.SIGTERM, aprsd_main.signal_handler) signal.signal(signal.SIGTERM, aprsd_main.signal_handler)
@ -50,19 +48,11 @@ def server(ctx, flush):
LOG.info(msg) LOG.info(msg)
LOG.info(f"APRSD Started version: {aprsd.__version__}") LOG.info(f"APRSD Started version: {aprsd.__version__}")
flat_config = utils.flatten_dict(config) CONF.log_opt_values(LOG, logging.DEBUG)
LOG.info("Using CONFIG values:")
for x in flat_config:
if "password" in x or "aprsd.web.users.admin" in x:
LOG.info(f"{x} = XXXXXXXXXXXXXXXXXXX")
else:
LOG.info(f"{x} = {flat_config[x]}")
stats.APRSDStats(config)
# Initialize the client factory and create # Initialize the client factory and create
# The correct client object ready for use # The correct client object ready for use
client.ClientFactory.setup(config) client.ClientFactory.setup()
# Make sure we have 1 client transport enabled # Make sure we have 1 client transport enabled
if not client.factory.is_client_enabled(): if not client.factory.is_client_enabled():
LOG.error("No Clients are enabled in config.") LOG.error("No Clients are enabled in config.")
@ -77,30 +67,28 @@ def server(ctx, flush):
client.factory.create().client client.factory.create().client
# Now load the msgTrack from disk if any # Now load the msgTrack from disk if any
packets.PacketList(config=config) packets.PacketList()
if flush: if flush:
LOG.debug("Deleting saved MsgTrack.") LOG.debug("Deleting saved MsgTrack.")
packets.PacketTrack(config=config).flush() packets.PacketTrack().flush()
packets.WatchList(config=config) packets.WatchList()
packets.SeenList(config=config) packets.SeenList()
else: else:
# Try and load saved MsgTrack list # Try and load saved MsgTrack list
LOG.debug("Loading saved MsgTrack object.") LOG.debug("Loading saved MsgTrack object.")
packets.PacketTrack(config=config).load() packets.PacketTrack().load()
packets.WatchList(config=config).load() packets.WatchList().load()
packets.SeenList(config=config).load() packets.SeenList().load()
# Create the initial PM singleton and Register plugins # Create the initial PM singleton and Register plugins
LOG.info("Loading Plugin Manager and registering plugins") LOG.info("Loading Plugin Manager and registering plugins")
plugin_manager = plugin.PluginManager(config) plugin_manager = plugin.PluginManager()
plugin_manager.setup_plugins() plugin_manager.setup_plugins()
rx_thread = rx.APRSDPluginRXThread( rx_thread = rx.APRSDPluginRXThread(
packet_queue=threads.packet_queue, packet_queue=threads.packet_queue,
config=config,
) )
process_thread = rx.APRSDPluginProcessPacketThread( process_thread = rx.APRSDPluginProcessPacketThread(
config=config,
packet_queue=threads.packet_queue, packet_queue=threads.packet_queue,
) )
rx_thread.start() rx_thread.start()
@ -108,19 +96,19 @@ def server(ctx, flush):
packets.PacketTrack().restart() packets.PacketTrack().restart()
keepalive = threads.KeepAliveThread(config=config) keepalive = threads.KeepAliveThread()
keepalive.start() keepalive.start()
web_enabled = config.get("aprsd.web.enabled", default=False) web_enabled = CONF.admin.web_enabled
if web_enabled: if web_enabled:
aprsd_main.flask_enabled = True aprsd_main.flask_enabled = True
(socketio, app) = flask.init_flask(config, loglevel, quiet) (socketio, app) = flask.init_flask(loglevel, quiet)
socketio.run( socketio.run(
app, app,
allow_unsafe_werkzeug=True, allow_unsafe_werkzeug=True,
host=config["aprsd"]["web"]["host"], host=CONF.admin.web_ip,
port=config["aprsd"]["web"]["port"], port=CONF.admin.web_port,
) )
# If there are items in the msgTracker, then save them # If there are items in the msgTracker, then save them

View File

@ -4,10 +4,13 @@ from logging.handlers import RotatingFileHandler
import queue import queue
import sys import sys
from oslo_config import cfg
from aprsd import config as aprsd_config from aprsd import config as aprsd_config
from aprsd.logging import rich as aprsd_logging from aprsd.logging import rich as aprsd_logging
CONF = cfg.CONF
LOG = logging.getLogger("APRSD") LOG = logging.getLogger("APRSD")
logging_queue = queue.Queue() logging_queue = queue.Queue()
@ -15,13 +18,15 @@ logging_queue = queue.Queue()
# Setup the logging faciility # Setup the logging faciility
# to disable logging to stdout, but still log to file # to disable logging to stdout, but still log to file
# use the --quiet option on the cmdln # use the --quiet option on the cmdln
def setup_logging(config, loglevel, quiet): def setup_logging(loglevel, quiet):
log_level = aprsd_config.LOG_LEVELS[loglevel] log_level = aprsd_config.LOG_LEVELS[loglevel]
LOG.setLevel(log_level) LOG.setLevel(log_level)
date_format = config["aprsd"].get("dateformat", aprsd_config.DEFAULT_DATE_FORMAT) date_format = CONF.logging.get("date_format", aprsd_config.DEFAULT_DATE_FORMAT)
rh = None
fh = None
rich_logging = False rich_logging = False
if config["aprsd"].get("rich_logging", False) and not quiet: if CONF.logging.get("rich_logging", False) and not quiet:
log_format = "%(message)s" log_format = "%(message)s"
log_formatter = logging.Formatter(fmt=log_format, datefmt=date_format) log_formatter = logging.Formatter(fmt=log_format, datefmt=date_format)
rh = aprsd_logging.APRSDRichHandler( rh = aprsd_logging.APRSDRichHandler(
@ -32,8 +37,8 @@ def setup_logging(config, loglevel, quiet):
LOG.addHandler(rh) LOG.addHandler(rh)
rich_logging = True rich_logging = True
log_file = config["aprsd"].get("logfile", None) log_file = CONF.logging.logfile
log_format = config["aprsd"].get("logformat", aprsd_config.DEFAULT_LOG_FORMAT) log_format = CONF.logging.logformat
log_formatter = logging.Formatter(fmt=log_format, datefmt=date_format) log_formatter = logging.Formatter(fmt=log_format, datefmt=date_format)
if log_file: if log_file:
@ -42,12 +47,16 @@ def setup_logging(config, loglevel, quiet):
LOG.addHandler(fh) LOG.addHandler(fh)
imap_logger = None imap_logger = None
if config.get("aprsd.email.enabled", default=False) and config.get("aprsd.email.imap.debug", default=False): if CONF.email_plugin.enabled and CONF.email_plugin.debug:
imap_logger = logging.getLogger("imapclient.imaplib") imap_logger = logging.getLogger("imapclient.imaplib")
imap_logger.setLevel(log_level) imap_logger.setLevel(log_level)
imap_logger.addHandler(fh) if rh:
imap_logger.addHandler(rh)
if fh:
imap_logger.addHandler(fh)
if config.get("aprsd.web.enabled", default=False):
if CONF.admin.get("web_enabled", default=False):
qh = logging.handlers.QueueHandler(logging_queue) qh = logging.handlers.QueueHandler(logging_queue)
q_log_formatter = logging.Formatter( q_log_formatter = logging.Formatter(
fmt=aprsd_config.QUEUE_LOG_FORMAT, fmt=aprsd_config.QUEUE_LOG_FORMAT,

View File

@ -1,12 +1,14 @@
import logging import logging
import threading import threading
from oslo_config import cfg
import wrapt import wrapt
from aprsd import stats, utils from aprsd import stats, utils
from aprsd.packets import seen_list from aprsd.packets import seen_list
CONF = cfg.CONF
LOG = logging.getLogger("APRSD") LOG = logging.getLogger("APRSD")
@ -15,7 +17,6 @@ class PacketList:
_instance = None _instance = None
lock = threading.Lock() lock = threading.Lock()
config = None
packet_list: utils.RingBuffer = utils.RingBuffer(1000) packet_list: utils.RingBuffer = utils.RingBuffer(1000)
@ -25,17 +26,8 @@ class PacketList:
def __new__(cls, *args, **kwargs): def __new__(cls, *args, **kwargs):
if cls._instance is None: if cls._instance is None:
cls._instance = super().__new__(cls) cls._instance = super().__new__(cls)
if "config" in kwargs:
cls._instance.config = kwargs["config"]
return cls._instance return cls._instance
def __init__(self, config=None):
if config:
self.config = config
def _is_initialized(self):
return self.config is not None
@wrapt.synchronized(lock) @wrapt.synchronized(lock)
def __iter__(self): def __iter__(self):
return iter(self.packet_list) return iter(self.packet_list)

View File

@ -2,11 +2,13 @@ import datetime
import logging import logging
import threading import threading
from oslo_config import cfg
import wrapt import wrapt
from aprsd.utils import objectstore from aprsd.utils import objectstore
CONF = cfg.CONF
LOG = logging.getLogger("APRSD") LOG = logging.getLogger("APRSD")
@ -16,21 +18,14 @@ class SeenList(objectstore.ObjectStoreMixin):
_instance = None _instance = None
lock = threading.Lock() lock = threading.Lock()
data: dict = {} data: dict = {}
config = None
def __new__(cls, *args, **kwargs): def __new__(cls, *args, **kwargs):
if cls._instance is None: if cls._instance is None:
cls._instance = super().__new__(cls) cls._instance = super().__new__(cls)
if "config" in kwargs: cls._instance._init_store()
if "config" in kwargs:
cls._instance.config = kwargs["config"]
cls._instance._init_store()
cls._instance.data = {} cls._instance.data = {}
return cls._instance return cls._instance
def is_initialized(self):
return self.config is not None
@wrapt.synchronized(lock) @wrapt.synchronized(lock)
def update_seen(self, packet): def update_seen(self, packet):
callsign = None callsign = None

View File

@ -1,12 +1,16 @@
import datetime import datetime
import threading import threading
from oslo_config import cfg
import wrapt import wrapt
from aprsd.threads import tx from aprsd.threads import tx
from aprsd.utils import objectstore from aprsd.utils import objectstore
CONF = cfg.CONF
class PacketTrack(objectstore.ObjectStoreMixin): class PacketTrack(objectstore.ObjectStoreMixin):
"""Class to keep track of outstanding text messages. """Class to keep track of outstanding text messages.
@ -23,7 +27,6 @@ class PacketTrack(objectstore.ObjectStoreMixin):
_instance = None _instance = None
_start_time = None _start_time = None
lock = threading.Lock() lock = threading.Lock()
config = None
data: dict = {} data: dict = {}
total_tracked: int = 0 total_tracked: int = 0
@ -32,14 +35,9 @@ class PacketTrack(objectstore.ObjectStoreMixin):
if cls._instance is None: if cls._instance is None:
cls._instance = super().__new__(cls) cls._instance = super().__new__(cls)
cls._instance._start_time = datetime.datetime.now() cls._instance._start_time = datetime.datetime.now()
if "config" in kwargs:
cls._instance.config = kwargs["config"]
cls._instance._init_store() cls._instance._init_store()
return cls._instance return cls._instance
def is_initialized(self):
return self.config is not None
@wrapt.synchronized(lock) @wrapt.synchronized(lock)
def __getitem__(self, name): def __getitem__(self, name):
return self.data[name] return self.data[name]

View File

@ -2,12 +2,14 @@ import datetime
import logging import logging
import threading import threading
from oslo_config import cfg
import wrapt import wrapt
from aprsd import utils from aprsd import utils
from aprsd.utils import objectstore from aprsd.utils import objectstore
CONF = cfg.CONF
LOG = logging.getLogger("APRSD") LOG = logging.getLogger("APRSD")
@ -17,24 +19,22 @@ class WatchList(objectstore.ObjectStoreMixin):
_instance = None _instance = None
lock = threading.Lock() lock = threading.Lock()
data = {} data = {}
config = None
def __new__(cls, *args, **kwargs): def __new__(cls, *args, **kwargs):
if cls._instance is None: if cls._instance is None:
cls._instance = super().__new__(cls) cls._instance = super().__new__(cls)
if "config" in kwargs: cls._instance._init_store()
cls._instance.config = kwargs["config"]
cls._instance._init_store()
cls._instance.data = {} cls._instance.data = {}
return cls._instance return cls._instance
def __init__(self, config=None): def __init__(self, config=None):
if config: ring_size = CONF.watch_list.packet_keep_count
self.config = config
ring_size = config["aprsd"]["watch_list"].get("packet_keep_count", 10) if not self.is_enabled():
LOG.info("Watch List is disabled.")
for callsign in config["aprsd"]["watch_list"].get("callsigns", []): if CONF.watch_list.callsigns:
for callsign in CONF.watch_list.callsigns:
call = callsign.replace("*", "") call = callsign.replace("*", "")
# FIXME(waboring) - we should fetch the last time we saw # FIXME(waboring) - we should fetch the last time we saw
# a beacon from a callsign or some other mechanism to find # a beacon from a callsign or some other mechanism to find
@ -47,14 +47,8 @@ class WatchList(objectstore.ObjectStoreMixin):
), ),
} }
def is_initialized(self):
return self.config is not None
def is_enabled(self): def is_enabled(self):
if self.config and "watch_list" in self.config["aprsd"]: return CONF.watch_list.enabled
return self.config["aprsd"]["watch_list"].get("enabled", False)
else:
return False
def callsign_in_watchlist(self, callsign): def callsign_in_watchlist(self, callsign):
return callsign in self.data return callsign in self.data
@ -78,9 +72,8 @@ class WatchList(objectstore.ObjectStoreMixin):
return str(now - self.last_seen(callsign)) return str(now - self.last_seen(callsign))
def max_delta(self, seconds=None): def max_delta(self, seconds=None):
watch_list_conf = self.config["aprsd"]["watch_list"]
if not seconds: if not seconds:
seconds = watch_list_conf["alert_time_seconds"] seconds = CONF.watch_list.alert_time_seconds
max_timeout = {"seconds": seconds} max_timeout = {"seconds": seconds}
return datetime.timedelta(**max_timeout) return datetime.timedelta(**max_timeout)

View File

@ -7,6 +7,7 @@ import re
import textwrap import textwrap
import threading import threading
from oslo_config import cfg
import pluggy import pluggy
import aprsd import aprsd
@ -15,6 +16,7 @@ from aprsd.packets import watch_list
# setup the global logger # setup the global logger
CONF = cfg.CONF
LOG = logging.getLogger("APRSD") LOG = logging.getLogger("APRSD")
CORE_MESSAGE_PLUGINS = [ CORE_MESSAGE_PLUGINS = [
@ -211,6 +213,10 @@ class APRSDRegexCommandPluginBase(APRSDPluginBase, metaclass=abc.ABCMeta):
@hookimpl @hookimpl
def filter(self, packet: packets.core.MessagePacket): def filter(self, packet: packets.core.MessagePacket):
if not self.enabled:
LOG.info(f"{self.__class__.__name__} is not enabled.")
return None
result = None result = None
message = packet.get("message_text", None) message = packet.get("message_text", None)
@ -220,7 +226,7 @@ class APRSDRegexCommandPluginBase(APRSDPluginBase, metaclass=abc.ABCMeta):
# Only process messages destined for us # Only process messages destined for us
# and is an APRS message format and has a message. # and is an APRS message format and has a message.
if ( if (
tocall == self.config["aprs"]["login"] tocall == CONF.callsign
and msg_format == "message" and msg_format == "message"
and message and message
): ):
@ -249,12 +255,11 @@ class APRSFIKEYMixin:
"""Mixin class to enable checking the existence of the aprs.fi apiKey.""" """Mixin class to enable checking the existence of the aprs.fi apiKey."""
def ensure_aprs_fi_key(self): def ensure_aprs_fi_key(self):
try: if not CONF.aprs_fi.apiKey:
self.config.check_option(["services", "aprs.fi", "apiKey"]) LOG.error("Config aprs_fi.apiKey is not set")
self.enabled = True
except Exception as ex:
LOG.error(f"Failed to find config aprs.fi:apikey {ex}")
self.enabled = False self.enabled = False
else:
self.enabled = True
class HelpPlugin(APRSDRegexCommandPluginBase): class HelpPlugin(APRSDRegexCommandPluginBase):
@ -410,21 +415,28 @@ class PluginManager:
) )
if plugin_obj: if plugin_obj:
if isinstance(plugin_obj, APRSDWatchListPluginBase): if isinstance(plugin_obj, APRSDWatchListPluginBase):
LOG.info( if plugin_obj.enabled:
"Registering WatchList plugin '{}'({})".format( LOG.info(
plugin_name, "Registering WatchList plugin '{}'({})".format(
plugin_obj.version, plugin_name,
), plugin_obj.version,
) ),
self._watchlist_pm.register(plugin_obj) )
self._watchlist_pm.register(plugin_obj)
else:
LOG.warning(f"Plugin {plugin_obj.__class__.__name__} is disabled")
else: else:
LOG.info( if plugin_obj.enabled:
"Registering plugin '{}'({})".format( LOG.info(
plugin_name, "Registering plugin '{}'({}) -- {}".format(
plugin_obj.version, plugin_name,
), plugin_obj.version,
) plugin_obj.command_regex,
self._pluggy_pm.register(plugin_obj) ),
)
self._pluggy_pm.register(plugin_obj)
else:
LOG.warning(f"Plugin {plugin_obj.__class__.__name__} is disabled")
except Exception as ex: except Exception as ex:
LOG.error(f"Couldn't load plugin '{plugin_name}'") LOG.error(f"Couldn't load plugin '{plugin_name}'")
LOG.exception(ex) LOG.exception(ex)
@ -443,7 +455,7 @@ class PluginManager:
_help = HelpPlugin(self.config) _help = HelpPlugin(self.config)
self._pluggy_pm.register(_help) self._pluggy_pm.register(_help)
enabled_plugins = self.config["aprsd"].get("enabled_plugins", None) enabled_plugins = CONF.enabled_plugins
if enabled_plugins: if enabled_plugins:
for p_name in enabled_plugins: for p_name in enabled_plugins:
self._load_plugin(p_name) self._load_plugin(p_name)

View File

@ -26,13 +26,18 @@ def get_aprs_fi(api_key, callsign):
def get_weather_gov_for_gps(lat, lon): def get_weather_gov_for_gps(lat, lon):
LOG.debug(f"Fetch station at {lat}, {lon}") LOG.debug(f"Fetch station at {lat}, {lon}")
headers = requests.utils.default_headers()
headers.update(
{"User-Agent": "(aprsd, waboring@hemna.com)"},
)
try: try:
url2 = ( url2 = (
"https://forecast.weather.gov/MapClick.php?lat=%s" #"https://forecast.weather.gov/MapClick.php?lat=%s"
"&lon=%s&FcstType=json" % (lat, lon) #"&lon=%s&FcstType=json" % (lat, lon)
f"https://api.weather.gov/points/{lat},{lon}"
) )
LOG.debug(f"Fetching weather '{url2}'") LOG.debug(f"Fetching weather '{url2}'")
response = requests.get(url2) response = requests.get(url2, headers=headers)
except Exception as e: except Exception as e:
LOG.error(e) LOG.error(e)
raise Exception("Failed to get weather") raise Exception("Failed to get weather")

View File

@ -9,13 +9,16 @@ import threading
import time import time
import imapclient import imapclient
from oslo_config import cfg
from aprsd import packets, plugin, stats, threads from aprsd import packets, plugin, stats, threads
from aprsd.threads import tx from aprsd.threads import tx
from aprsd.utils import trace from aprsd.utils import trace
CONF = cfg.CONF
LOG = logging.getLogger("APRSD") LOG = logging.getLogger("APRSD")
shortcuts_dict = None
class EmailInfo: class EmailInfo:
@ -71,18 +74,18 @@ class EmailPlugin(plugin.APRSDRegexCommandPluginBase):
def setup(self): def setup(self):
"""Ensure that email is enabled and start the thread.""" """Ensure that email is enabled and start the thread."""
if CONF.email_plugin.enabled:
email_enabled = self.config["aprsd"]["email"].get("enabled", False)
if email_enabled:
self.enabled = True self.enabled = True
shortcuts = _build_shortcuts_dict()
LOG.info(f"Email shortcuts {shortcuts}")
else: else:
LOG.info("Email services not enabled.") LOG.info("Email services not enabled.")
self.enabled = False
def create_threads(self): def create_threads(self):
if self.enabled: if self.enabled:
return APRSDEmailThread( return APRSDEmailThread()
config=self.config,
)
@trace.trace @trace.trace
def process(self, packet: packets.MessagePacket): def process(self, packet: packets.MessagePacket):
@ -97,18 +100,18 @@ class EmailPlugin(plugin.APRSDRegexCommandPluginBase):
ack = packet.get("msgNo", "0") ack = packet.get("msgNo", "0")
reply = None reply = None
if not self.config["aprsd"]["email"].get("enabled", False): if not CONF.email_plugin.enabled:
LOG.debug("Email is not enabled in config file ignoring.") LOG.debug("Email is not enabled in config file ignoring.")
return "Email not enabled." return "Email not enabled."
searchstring = "^" + self.config["ham"]["callsign"] + ".*" searchstring = "^" + CONF.email_plugin.callsign + ".*"
# only I can do email # only I can do email
if re.search(searchstring, fromcall): if re.search(searchstring, fromcall):
# digits only, first one is number of emails to resend # digits only, first one is number of emails to resend
r = re.search("^-([0-9])[0-9]*$", message) r = re.search("^-([0-9])[0-9]*$", message)
if r is not None: if r is not None:
LOG.debug("RESEND EMAIL") LOG.debug("RESEND EMAIL")
resend_email(self.config, r.group(1), fromcall) resend_email(r.group(1), fromcall)
reply = packets.NULL_MESSAGE reply = packets.NULL_MESSAGE
# -user@address.com body of email # -user@address.com body of email
elif re.search(r"^-([A-Za-z0-9_\-\.@]+) (.*)", message): elif re.search(r"^-([A-Za-z0-9_\-\.@]+) (.*)", message):
@ -118,7 +121,7 @@ class EmailPlugin(plugin.APRSDRegexCommandPluginBase):
to_addr = a.group(1) to_addr = a.group(1)
content = a.group(2) content = a.group(2)
email_address = get_email_from_shortcut(self.config, to_addr) email_address = get_email_from_shortcut(to_addr)
if not email_address: if not email_address:
reply = "Bad email address" reply = "Bad email address"
return reply return reply
@ -128,7 +131,7 @@ class EmailPlugin(plugin.APRSDRegexCommandPluginBase):
content = ( content = (
"Click for my location: http://aprs.fi/{}" "" "Click for my location: http://aprs.fi/{}" ""
).format( ).format(
self.config["ham"]["callsign"], CONF.email_plugin.callsign,
) )
too_soon = 0 too_soon = 0
now = time.time() now = time.time()
@ -141,7 +144,7 @@ class EmailPlugin(plugin.APRSDRegexCommandPluginBase):
too_soon = 1 too_soon = 1
if not too_soon or ack == 0: if not too_soon or ack == 0:
LOG.info(f"Send email '{content}'") LOG.info(f"Send email '{content}'")
send_result = send_email(self.config, to_addr, content) send_result = send_email(to_addr, content)
reply = packets.NULL_MESSAGE reply = packets.NULL_MESSAGE
if send_result != 0: if send_result != 0:
reply = f"-{to_addr} failed" reply = f"-{to_addr} failed"
@ -169,9 +172,9 @@ class EmailPlugin(plugin.APRSDRegexCommandPluginBase):
return reply return reply
def _imap_connect(config): def _imap_connect():
imap_port = config["aprsd"]["email"]["imap"].get("port", 143) imap_port = CONF.email_plugin.imap_port
use_ssl = config["aprsd"]["email"]["imap"].get("use_ssl", False) use_ssl = CONF.email_plugin.imap_use_ssl
# host = CONFIG["aprsd"]["email"]["imap"]["host"] # host = CONFIG["aprsd"]["email"]["imap"]["host"]
# msg = "{}{}:{}".format("TLS " if use_ssl else "", host, imap_port) # msg = "{}{}:{}".format("TLS " if use_ssl else "", host, imap_port)
# LOG.debug("Connect to IMAP host {} with user '{}'". # LOG.debug("Connect to IMAP host {} with user '{}'".
@ -179,7 +182,7 @@ def _imap_connect(config):
try: try:
server = imapclient.IMAPClient( server = imapclient.IMAPClient(
config["aprsd"]["email"]["imap"]["host"], CONF.email_plugin.imap_host,
port=imap_port, port=imap_port,
use_uid=True, use_uid=True,
ssl=use_ssl, ssl=use_ssl,
@ -191,8 +194,8 @@ def _imap_connect(config):
try: try:
server.login( server.login(
config["aprsd"]["email"]["imap"]["login"], CONF.email_plugin.imap_login,
config["aprsd"]["email"]["imap"]["password"], CONF.email_plugin.imap_password,
) )
except (imaplib.IMAP4.error, Exception) as e: except (imaplib.IMAP4.error, Exception) as e:
msg = getattr(e, "message", repr(e)) msg = getattr(e, "message", repr(e))
@ -208,15 +211,15 @@ def _imap_connect(config):
return server return server
def _smtp_connect(config): def _smtp_connect():
host = config["aprsd"]["email"]["smtp"]["host"] host = CONF.email_plugin.smtp_host
smtp_port = config["aprsd"]["email"]["smtp"]["port"] smtp_port = CONF.email_plugin.smtp_port
use_ssl = config["aprsd"]["email"]["smtp"].get("use_ssl", False) use_ssl = CONF.email_plugin.smtp_use_ssl
msg = "{}{}:{}".format("SSL " if use_ssl else "", host, smtp_port) msg = "{}{}:{}".format("SSL " if use_ssl else "", host, smtp_port)
LOG.debug( LOG.debug(
"Connect to SMTP host {} with user '{}'".format( "Connect to SMTP host {} with user '{}'".format(
msg, msg,
config["aprsd"]["email"]["imap"]["login"], CONF.email_plugin.smtp_login,
), ),
) )
@ -239,15 +242,15 @@ def _smtp_connect(config):
LOG.debug(f"Connected to smtp host {msg}") LOG.debug(f"Connected to smtp host {msg}")
debug = config["aprsd"]["email"]["smtp"].get("debug", False) debug = CONF.email_plugin.debug
if debug: if debug:
server.set_debuglevel(5) server.set_debuglevel(5)
server.sendmail = trace.trace(server.sendmail) server.sendmail = trace.trace(server.sendmail)
try: try:
server.login( server.login(
config["aprsd"]["email"]["smtp"]["login"], CONF.email_plugin.smtp_login,
config["aprsd"]["email"]["smtp"]["password"], CONF.email_plugin.smtp_password,
) )
except Exception: except Exception:
LOG.error("Couldn't connect to SMTP Server") LOG.error("Couldn't connect to SMTP Server")
@ -257,22 +260,40 @@ def _smtp_connect(config):
return server return server
def get_email_from_shortcut(config, addr): def _build_shortcuts_dict():
if config["aprsd"]["email"].get("shortcuts", False): global shortcuts_dict
return config["aprsd"]["email"]["shortcuts"].get(addr, addr) if not shortcuts_dict:
if CONF.email_plugin.email_shortcuts:
shortcuts_dict = {}
tmp = CONF.email_plugin.email_shortcuts
for combo in tmp:
entry = combo.split("=")
shortcuts_dict[entry[0]] = entry[1]
else:
shortcuts_dict = {}
LOG.info(f"Shortcuts Dict {shortcuts_dict}")
return shortcuts_dict
def get_email_from_shortcut(addr):
if CONF.email_plugin.email_shortcuts:
shortcuts = _build_shortcuts_dict()
LOG.info(f"Shortcut lookup {addr} returns {shortcuts.get(addr, addr)}")
return shortcuts.get(addr, addr)
else: else:
return addr return addr
def validate_email_config(config, disable_validation=False): def validate_email_config(disable_validation=False):
"""function to simply ensure we can connect to email services. """function to simply ensure we can connect to email services.
This helps with failing early during startup. This helps with failing early during startup.
""" """
LOG.info("Checking IMAP configuration") LOG.info("Checking IMAP configuration")
imap_server = _imap_connect(config) imap_server = _imap_connect()
LOG.info("Checking SMTP configuration") LOG.info("Checking SMTP configuration")
smtp_server = _smtp_connect(config) smtp_server = _smtp_connect()
if imap_server and smtp_server: if imap_server and smtp_server:
return True return True
@ -376,16 +397,16 @@ def parse_email(msgid, data, server):
@trace.trace @trace.trace
def send_email(config, to_addr, content): def send_email(to_addr, content):
shortcuts = config["aprsd"]["email"]["shortcuts"] shortcuts = _build_shortcuts_dict()
email_address = get_email_from_shortcut(config, to_addr) email_address = get_email_from_shortcut(to_addr)
LOG.info("Sending Email_________________") LOG.info("Sending Email_________________")
if to_addr in shortcuts: if to_addr in shortcuts:
LOG.info(f"To : {to_addr}") LOG.info(f"To : {to_addr}")
to_addr = email_address to_addr = email_address
LOG.info(f" ({to_addr})") LOG.info(f" ({to_addr})")
subject = config["ham"]["callsign"] subject = CONF.email_plugin.callsign
# content = content + "\n\n(NOTE: reply with one line)" # content = content + "\n\n(NOTE: reply with one line)"
LOG.info(f"Subject : {subject}") LOG.info(f"Subject : {subject}")
LOG.info(f"Body : {content}") LOG.info(f"Body : {content}")
@ -395,13 +416,13 @@ def send_email(config, to_addr, content):
msg = MIMEText(content) msg = MIMEText(content)
msg["Subject"] = subject msg["Subject"] = subject
msg["From"] = config["aprsd"]["email"]["smtp"]["login"] msg["From"] = CONF.email_plugin.smtp_login
msg["To"] = to_addr msg["To"] = to_addr
server = _smtp_connect(config) server = _smtp_connect()
if server: if server:
try: try:
server.sendmail( server.sendmail(
config["aprsd"]["email"]["smtp"]["login"], CONF.email_plugin.smtp_login,
[to_addr], [to_addr],
msg.as_string(), msg.as_string(),
) )
@ -415,19 +436,19 @@ def send_email(config, to_addr, content):
@trace.trace @trace.trace
def resend_email(config, count, fromcall): def resend_email(count, fromcall):
date = datetime.datetime.now() date = datetime.datetime.now()
month = date.strftime("%B")[:3] # Nov, Mar, Apr month = date.strftime("%B")[:3] # Nov, Mar, Apr
day = date.day day = date.day
year = date.year year = date.year
today = f"{day}-{month}-{year}" today = f"{day}-{month}-{year}"
shortcuts = config["aprsd"]["email"]["shortcuts"] shortcuts = _build_shortcuts_dict()
# swap key/value # swap key/value
shortcuts_inverted = {v: k for k, v in shortcuts.items()} shortcuts_inverted = {v: k for k, v in shortcuts.items()}
try: try:
server = _imap_connect(config) server = _imap_connect()
except Exception: except Exception:
LOG.exception("Failed to Connect to IMAP. Cannot resend email ") LOG.exception("Failed to Connect to IMAP. Cannot resend email ")
return return
@ -467,7 +488,7 @@ def resend_email(config, count, fromcall):
reply = "-" + from_addr + " * " + body.decode(errors="ignore") reply = "-" + from_addr + " * " + body.decode(errors="ignore")
tx.send( tx.send(
packets.MessagePacket( packets.MessagePacket(
from_call=config["aprsd"]["callsign"], from_call=CONF.callsign,
to_call=fromcall, to_call=fromcall,
message_text=reply, message_text=reply,
), ),
@ -490,7 +511,7 @@ def resend_email(config, count, fromcall):
) )
tx.send( tx.send(
packets.MessagePacket( packets.MessagePacket(
from_call=config["aprsd"]["callsign"], from_call=CONF.callsign,
to_call=fromcall, to_call=fromcall,
message_text=reply, message_text=reply,
), ),
@ -504,9 +525,8 @@ def resend_email(config, count, fromcall):
class APRSDEmailThread(threads.APRSDThread): class APRSDEmailThread(threads.APRSDThread):
def __init__(self, config): def __init__(self):
super().__init__("EmailThread") super().__init__("EmailThread")
self.config = config
self.past = datetime.datetime.now() self.past = datetime.datetime.now()
def loop(self): def loop(self):
@ -527,7 +547,7 @@ class APRSDEmailThread(threads.APRSDThread):
f"check_email_delay is {EmailInfo().delay} seconds ", f"check_email_delay is {EmailInfo().delay} seconds ",
) )
shortcuts = self.config["aprsd"]["email"]["shortcuts"] shortcuts = _build_shortcuts_dict()
# swap key/value # swap key/value
shortcuts_inverted = {v: k for k, v in shortcuts.items()} shortcuts_inverted = {v: k for k, v in shortcuts.items()}
@ -538,7 +558,7 @@ class APRSDEmailThread(threads.APRSDThread):
today = f"{day}-{month}-{year}" today = f"{day}-{month}-{year}"
try: try:
server = _imap_connect(self.config) server = _imap_connect()
except Exception: except Exception:
LOG.exception("IMAP Failed to connect") LOG.exception("IMAP Failed to connect")
return True return True
@ -611,8 +631,8 @@ class APRSDEmailThread(threads.APRSDThread):
# config ham.callsign # config ham.callsign
tx.send( tx.send(
packets.MessagePacket( packets.MessagePacket(
from_call=self.config["aprsd"]["callsign"], from_call=CONF.callsign,
to_call=self.config["ham"]["callsign"], to_call=CONF.email_plugin.callsign,
message_text=reply, message_text=reply,
), ),
) )

View File

@ -2,10 +2,13 @@ import logging
import re import re
import time import time
from oslo_config import cfg
from aprsd import packets, plugin, plugin_utils from aprsd import packets, plugin, plugin_utils
from aprsd.utils import trace from aprsd.utils import trace
CONF = cfg.CONF
LOG = logging.getLogger("APRSD") LOG = logging.getLogger("APRSD")
@ -26,7 +29,7 @@ class LocationPlugin(plugin.APRSDRegexCommandPluginBase, plugin.APRSFIKEYMixin):
message = packet.get("message_text", None) message = packet.get("message_text", None)
# ack = packet.get("msgNo", "0") # ack = packet.get("msgNo", "0")
api_key = self.config["services"]["aprs.fi"]["apiKey"] api_key = CONF.aprs_fi.apiKey
# optional second argument is a callsign to search # optional second argument is a callsign to search
a = re.search(r"^.*\s+(.*)", message) a = re.search(r"^.*\s+(.*)", message)

View File

@ -1,8 +1,11 @@
import logging import logging
from oslo_config import cfg
from aprsd import packets, plugin from aprsd import packets, plugin
CONF = cfg.CONF
LOG = logging.getLogger("APRSD") LOG = logging.getLogger("APRSD")
@ -20,7 +23,7 @@ class NotifySeenPlugin(plugin.APRSDWatchListPluginBase):
def process(self, packet: packets.MessagePacket): def process(self, packet: packets.MessagePacket):
LOG.info("NotifySeenPlugin") LOG.info("NotifySeenPlugin")
notify_callsign = self.config["aprsd"]["watch_list"]["alert_callsign"] notify_callsign = CONF.watch_list.alert_callsign
fromcall = packet.from_call fromcall = packet.from_call
wl = packets.WatchList() wl = packets.WatchList()
@ -38,7 +41,7 @@ class NotifySeenPlugin(plugin.APRSDWatchListPluginBase):
packet_type = packet.__class__.__name__ packet_type = packet.__class__.__name__
# we shouldn't notify the alert user that they are online. # we shouldn't notify the alert user that they are online.
pkt = packets.MessagePacket( pkt = packets.MessagePacket(
from_call=self.config["aprsd"]["callsign"], from_call=CONF.callsign,
to_call=notify_callsign, to_call=notify_callsign,
message_text=( message_text=(
f"{fromcall} was just seen by type:'{packet_type}'" f"{fromcall} was just seen by type:'{packet_type}'"

View File

@ -2,11 +2,14 @@ import datetime
import logging import logging
import re import re
from oslo_config import cfg
from aprsd import packets, plugin from aprsd import packets, plugin
from aprsd.packets import tracker from aprsd.packets import tracker
from aprsd.utils import trace from aprsd.utils import trace
CONF = cfg.CONF
LOG = logging.getLogger("APRSD") LOG = logging.getLogger("APRSD")
@ -17,6 +20,13 @@ class QueryPlugin(plugin.APRSDRegexCommandPluginBase):
command_name = "query" command_name = "query"
short_description = "APRSD Owner command to query messages in the MsgTrack" short_description = "APRSD Owner command to query messages in the MsgTrack"
def setup(self):
"""Do any plugin setup here."""
if not CONF.query_plugin.callsign:
LOG.error("Config query_plugin.callsign not set. Disabling plugin")
self.enabled = False
self.enabled = True
@trace.trace @trace.trace
def process(self, packet: packets.MessagePacket): def process(self, packet: packets.MessagePacket):
LOG.info("Query COMMAND") LOG.info("Query COMMAND")
@ -32,7 +42,7 @@ class QueryPlugin(plugin.APRSDRegexCommandPluginBase):
now.strftime("%H:%M:%S"), now.strftime("%H:%M:%S"),
) )
searchstring = "^" + self.config["ham"]["callsign"] + ".*" searchstring = "^" + CONF.query_plugin.callsign + ".*"
# only I can do admin commands # only I can do admin commands
if re.search(searchstring, fromcall): if re.search(searchstring, fromcall):

View File

@ -2,12 +2,14 @@ import logging
import re import re
import time import time
from oslo_config import cfg
import pytz import pytz
from aprsd import packets, plugin, plugin_utils from aprsd import packets, plugin, plugin_utils
from aprsd.utils import fuzzy, trace from aprsd.utils import fuzzy, trace
CONF = cfg.CONF
LOG = logging.getLogger("APRSD") LOG = logging.getLogger("APRSD")
@ -74,7 +76,7 @@ class TimeOWMPlugin(TimePlugin, plugin.APRSFIKEYMixin):
# if no second argument, search for calling station # if no second argument, search for calling station
searchcall = fromcall searchcall = fromcall
api_key = self.config["services"]["aprs.fi"]["apiKey"] api_key = CONF.aprs_fi.apiKey
try: try:
aprs_data = plugin_utils.get_aprs_fi(api_key, searchcall) aprs_data = plugin_utils.get_aprs_fi(api_key, searchcall)
except Exception as ex: except Exception as ex:

View File

@ -2,12 +2,14 @@ import json
import logging import logging
import re import re
from oslo_config import cfg
import requests import requests
from aprsd import plugin, plugin_utils from aprsd import plugin, plugin_utils
from aprsd.utils import trace from aprsd.utils import trace
CONF = cfg.CONF
LOG = logging.getLogger("APRSD") LOG = logging.getLogger("APRSD")
@ -34,10 +36,10 @@ class USWeatherPlugin(plugin.APRSDRegexCommandPluginBase, plugin.APRSFIKEYMixin)
@trace.trace @trace.trace
def process(self, packet): def process(self, packet):
LOG.info("Weather Plugin") LOG.info("Weather Plugin")
fromcall = packet.get("from") fromcall = packet.from_call
# message = packet.get("message_text", None) # message = packet.get("message_text", None)
# ack = packet.get("msgNo", "0") # ack = packet.get("msgNo", "0")
api_key = self.config["services"]["aprs.fi"]["apiKey"] api_key = CONF.aprs_fi.apiKey
try: try:
aprs_data = plugin_utils.get_aprs_fi(api_key, fromcall) aprs_data = plugin_utils.get_aprs_fi(api_key, fromcall)
except Exception as ex: except Exception as ex:
@ -58,17 +60,23 @@ class USWeatherPlugin(plugin.APRSDRegexCommandPluginBase, plugin.APRSFIKEYMixin)
LOG.error(f"Couldn't fetch forecast.weather.gov '{ex}'") LOG.error(f"Couldn't fetch forecast.weather.gov '{ex}'")
return "Unable to get weather" return "Unable to get weather"
reply = ( LOG.info(f"WX data {wx_data}")
"%sF(%sF/%sF) %s. %s, %s."
% ( if wx_data["success"] == False:
wx_data["currentobservation"]["Temp"], # Failed to fetch the weather
wx_data["data"]["temperature"][0], reply = "Failed to fetch weather for location"
wx_data["data"]["temperature"][1], else:
wx_data["data"]["weather"][0], reply = (
wx_data["time"]["startPeriodName"][1], "%sF(%sF/%sF) %s. %s, %s."
wx_data["data"]["weather"][1], % (
) wx_data["currentobservation"]["Temp"],
).rstrip() wx_data["data"]["temperature"][0],
wx_data["data"]["temperature"][1],
wx_data["data"]["weather"][0],
wx_data["time"]["startPeriodName"][1],
wx_data["data"]["weather"][1],
)
).rstrip()
LOG.debug(f"reply: '{reply}' ") LOG.debug(f"reply: '{reply}' ")
return reply return reply
@ -119,13 +127,7 @@ class USMetarPlugin(plugin.APRSDRegexCommandPluginBase, plugin.APRSFIKEYMixin):
# if no second argument, search for calling station # if no second argument, search for calling station
fromcall = fromcall fromcall = fromcall
try: api_key = CONF.aprs_fi.apiKey
self.config.exists(["services", "aprs.fi", "apiKey"])
except Exception as ex:
LOG.error(f"Failed to find config aprs.fi:apikey {ex}")
return "No aprs.fi apikey found"
api_key = self.config["services"]["aprs.fi"]["apiKey"]
try: try:
aprs_data = plugin_utils.get_aprs_fi(api_key, fromcall) aprs_data = plugin_utils.get_aprs_fi(api_key, fromcall)
@ -187,6 +189,13 @@ class OWMWeatherPlugin(plugin.APRSDRegexCommandPluginBase):
command_name = "OpenWeatherMap" command_name = "OpenWeatherMap"
short_description = "OpenWeatherMap weather of GPS Beacon location" short_description = "OpenWeatherMap weather of GPS Beacon location"
def setup(self):
if not CONF.owm_weather_plugin.apiKey:
LOG.error("Config.owm_weather_plugin.apiKey is not set. Disabling")
self.enabled = False
else:
self.enabled = True
def help(self): def help(self):
_help = [ _help = [
"openweathermap: Send {} to get weather " "openweathermap: Send {} to get weather "
@ -209,13 +218,8 @@ class OWMWeatherPlugin(plugin.APRSDRegexCommandPluginBase):
else: else:
searchcall = fromcall searchcall = fromcall
try: api_key = CONF.aprs_fi.apiKey
self.config.exists(["services", "aprs.fi", "apiKey"])
except Exception as ex:
LOG.error(f"Failed to find config aprs.fi:apikey {ex}")
return "No aprs.fi apikey found"
api_key = self.config["services"]["aprs.fi"]["apiKey"]
try: try:
aprs_data = plugin_utils.get_aprs_fi(api_key, searchcall) aprs_data = plugin_utils.get_aprs_fi(api_key, searchcall)
except Exception as ex: except Exception as ex:
@ -230,21 +234,8 @@ class OWMWeatherPlugin(plugin.APRSDRegexCommandPluginBase):
lat = aprs_data["entries"][0]["lat"] lat = aprs_data["entries"][0]["lat"]
lon = aprs_data["entries"][0]["lng"] lon = aprs_data["entries"][0]["lng"]
try: units = CONF.units
self.config.exists(["services", "openweathermap", "apiKey"]) api_key = CONF.owm_weather_plugin.apiKey
except Exception as ex:
LOG.error(f"Failed to find config openweathermap:apiKey {ex}")
return "No openweathermap apiKey found"
try:
self.config.exists(["aprsd", "units"])
except Exception:
LOG.debug("Couldn't find untis in aprsd:services:units")
units = "metric"
else:
units = self.config["aprsd"]["units"]
api_key = self.config["services"]["openweathermap"]["apiKey"]
try: try:
wx_data = plugin_utils.fetch_openweathermap( wx_data = plugin_utils.fetch_openweathermap(
api_key, api_key,
@ -317,6 +308,16 @@ class AVWXWeatherPlugin(plugin.APRSDRegexCommandPluginBase):
command_name = "AVWXWeather" command_name = "AVWXWeather"
short_description = "AVWX weather of GPS Beacon location" short_description = "AVWX weather of GPS Beacon location"
def setup(self):
if not CONF.avwx_plugin.base_url:
LOG.error("Config avwx_plugin.base_url not specified. Disabling")
return False
elif not CONF.avwx_plugin.apiKey:
LOG.error("Config avwx_plugin.apiKey not specified. Disabling")
return False
else:
return True
def help(self): def help(self):
_help = [ _help = [
"avwxweather: Send {} to get weather " "avwxweather: Send {} to get weather "
@ -339,13 +340,7 @@ class AVWXWeatherPlugin(plugin.APRSDRegexCommandPluginBase):
else: else:
searchcall = fromcall searchcall = fromcall
try: api_key = CONF.aprs_fi.apiKey
self.config.exists(["services", "aprs.fi", "apiKey"])
except Exception as ex:
LOG.error(f"Failed to find config aprs.fi:apikey {ex}")
return "No aprs.fi apikey found"
api_key = self.config["services"]["aprs.fi"]["apiKey"]
try: try:
aprs_data = plugin_utils.get_aprs_fi(api_key, searchcall) aprs_data = plugin_utils.get_aprs_fi(api_key, searchcall)
except Exception as ex: except Exception as ex:
@ -360,21 +355,8 @@ class AVWXWeatherPlugin(plugin.APRSDRegexCommandPluginBase):
lat = aprs_data["entries"][0]["lat"] lat = aprs_data["entries"][0]["lat"]
lon = aprs_data["entries"][0]["lng"] lon = aprs_data["entries"][0]["lng"]
try: api_key = CONF.avwx_plugin.apiKey
self.config.exists(["services", "avwx", "apiKey"]) base_url = CONF.avwx_plugin.base_url
except Exception as ex:
LOG.error(f"Failed to find config avwx:apiKey {ex}")
return "No avwx apiKey found"
try:
self.config.exists(self.config, ["services", "avwx", "base_url"])
except Exception as ex:
LOG.debug(f"Didn't find avwx:base_url {ex}")
base_url = "https://avwx.rest"
else:
base_url = self.config["services"]["avwx"]["base_url"]
api_key = self.config["services"]["avwx"]["apiKey"]
token = f"TOKEN {api_key}" token = f"TOKEN {api_key}"
headers = {"Authorization": token} headers = {"Authorization": token}
try: try:

View File

@ -2,12 +2,14 @@ import datetime
import logging import logging
import threading import threading
from oslo_config import cfg
import wrapt import wrapt
import aprsd import aprsd
from aprsd import packets, plugin, utils from aprsd import packets, plugin, utils
CONF = cfg.CONF
LOG = logging.getLogger("APRSD") LOG = logging.getLogger("APRSD")
@ -15,7 +17,6 @@ class APRSDStats:
_instance = None _instance = None
lock = threading.Lock() lock = threading.Lock()
config = None
start_time = None start_time = None
_aprsis_server = None _aprsis_server = None
@ -67,10 +68,6 @@ class APRSDStats:
cls._instance._aprsis_keepalive = datetime.datetime.now() cls._instance._aprsis_keepalive = datetime.datetime.now()
return cls._instance return cls._instance
def __init__(self, config=None):
if config:
self.config = config
@wrapt.synchronized(lock) @wrapt.synchronized(lock)
@property @property
def uptime(self): def uptime(self):
@ -191,7 +188,7 @@ class APRSDStats:
"aprsd": { "aprsd": {
"version": aprsd.__version__, "version": aprsd.__version__,
"uptime": utils.strfdelta(self.uptime), "uptime": utils.strfdelta(self.uptime),
"callsign": self.config["aprsd"]["callsign"], "callsign": CONF.callsign,
"memory_current": int(self.memory), "memory_current": int(self.memory),
"memory_current_str": utils.human_size(self.memory), "memory_current_str": utils.human_size(self.memory),
"memory_peak": int(self.memory_peak), "memory_peak": int(self.memory_peak),
@ -201,7 +198,7 @@ class APRSDStats:
}, },
"aprs-is": { "aprs-is": {
"server": str(self.aprsis_server), "server": str(self.aprsis_server),
"callsign": self.config["aprs"]["login"], "callsign": CONF.aprs_network.login,
"last_update": last_aprsis_keepalive, "last_update": last_aprsis_keepalive,
}, },
"packets": { "packets": {
@ -215,7 +212,7 @@ class APRSDStats:
"ack_sent": self._pkt_cnt["AckPacket"]["tx"], "ack_sent": self._pkt_cnt["AckPacket"]["tx"],
}, },
"email": { "email": {
"enabled": self.config["aprsd"]["email"]["enabled"], "enabled": CONF.email_plugin.enabled,
"sent": int(self._email_tx), "sent": int(self._email_tx),
"received": int(self._email_rx), "received": int(self._email_rx),
"thread_last_update": last_update, "thread_last_update": last_update,

View File

@ -3,10 +3,13 @@ import logging
import time import time
import tracemalloc import tracemalloc
from oslo_config import cfg
from aprsd import client, packets, stats, utils from aprsd import client, packets, stats, utils
from aprsd.threads import APRSDThread, APRSDThreadList from aprsd.threads import APRSDThread, APRSDThreadList
CONF = cfg.CONF
LOG = logging.getLogger("APRSD") LOG = logging.getLogger("APRSD")
@ -14,10 +17,9 @@ class KeepAliveThread(APRSDThread):
cntr = 0 cntr = 0
checker_time = datetime.datetime.now() checker_time = datetime.datetime.now()
def __init__(self, config): def __init__(self):
tracemalloc.start() tracemalloc.start()
super().__init__("KeepAlive") super().__init__("KeepAlive")
self.config = config
max_timeout = {"hours": 0.0, "minutes": 2, "seconds": 0} max_timeout = {"hours": 0.0, "minutes": 2, "seconds": 0}
self.max_delta = datetime.timedelta(**max_timeout) self.max_delta = datetime.timedelta(**max_timeout)
@ -40,15 +42,9 @@ class KeepAliveThread(APRSDThread):
stats_obj.set_memory(current) stats_obj.set_memory(current)
stats_obj.set_memory_peak(peak) stats_obj.set_memory_peak(peak)
try: login = CONF.callsign
login = self.config["aprsd"]["callsign"]
except KeyError:
login = self.config["ham"]["callsign"]
if pkt_tracker.is_initialized(): tracked_packets = len(pkt_tracker)
tracked_packets = len(pkt_tracker)
else:
tracked_packets = 0
keepalive = ( keepalive = (
"{} - Uptime {} RX:{} TX:{} Tracker:{} Msgs TX:{} RX:{} " "{} - Uptime {} RX:{} TX:{} Tracker:{} Msgs TX:{} RX:{} "
@ -77,7 +73,7 @@ class KeepAliveThread(APRSDThread):
if delta > self.max_delta: if delta > self.max_delta:
# We haven't gotten a keepalive from aprs-is in a while # We haven't gotten a keepalive from aprs-is in a while
# reset the connection.a # reset the connection.a
if not client.KISSClient.is_enabled(self.config): if not client.KISSClient.is_enabled():
LOG.warning(f"Resetting connection to APRS-IS {delta}") LOG.warning(f"Resetting connection to APRS-IS {delta}")
client.factory.create().reset() client.factory.create().reset()

View File

@ -4,18 +4,19 @@ import queue
import time import time
import aprslib import aprslib
from oslo_config import cfg
from aprsd import client, packets, plugin from aprsd import client, packets, plugin
from aprsd.threads import APRSDThread, tx from aprsd.threads import APRSDThread, tx
CONF = cfg.CONF
LOG = logging.getLogger("APRSD") LOG = logging.getLogger("APRSD")
class APRSDRXThread(APRSDThread): class APRSDRXThread(APRSDThread):
def __init__(self, config, packet_queue): def __init__(self, packet_queue):
super().__init__("RX_MSG") super().__init__("RX_MSG")
self.config = config
self.packet_queue = packet_queue self.packet_queue = packet_queue
self._client = client.factory.create() self._client = client.factory.create()
@ -80,8 +81,7 @@ class APRSDProcessPacketThread(APRSDThread):
will ack a message before sending the packet to the subclass will ack a message before sending the packet to the subclass
for processing.""" for processing."""
def __init__(self, config, packet_queue): def __init__(self, packet_queue):
self.config = config
self.packet_queue = packet_queue self.packet_queue = packet_queue
super().__init__("ProcessPKT") super().__init__("ProcessPKT")
self._loop_cnt = 1 self._loop_cnt = 1
@ -106,7 +106,7 @@ class APRSDProcessPacketThread(APRSDThread):
def process_packet(self, packet): def process_packet(self, packet):
"""Process a packet received from aprs-is server.""" """Process a packet received from aprs-is server."""
LOG.debug(f"RXPKT-LOOP {self._loop_cnt}") LOG.debug(f"RXPKT-LOOP {self._loop_cnt}")
our_call = self.config["aprsd"]["callsign"].lower() our_call = CONF.callsign.lower()
from_call = packet.from_call from_call = packet.from_call
if packet.addresse: if packet.addresse:
@ -133,7 +133,7 @@ class APRSDProcessPacketThread(APRSDThread):
# send an ack last # send an ack last
tx.send( tx.send(
packets.AckPacket( packets.AckPacket(
from_call=self.config["aprsd"]["callsign"], from_call=CONF.callsign,
to_call=from_call, to_call=from_call,
msgNo=msg_id, msgNo=msg_id,
), ),
@ -178,11 +178,11 @@ class APRSDPluginProcessPacketThread(APRSDProcessPacketThread):
if isinstance(subreply, packets.Packet): if isinstance(subreply, packets.Packet):
tx.send(subreply) tx.send(subreply)
else: else:
wl = self.config["aprsd"]["watch_list"] wl = CONF.watch_list
to_call = wl["alert_callsign"] to_call = wl["alert_callsign"]
tx.send( tx.send(
packets.MessagePacket( packets.MessagePacket(
from_call=self.config["aprsd"]["callsign"], from_call=CONF.callsign,
to_call=to_call, to_call=to_call,
message_text=subreply, message_text=subreply,
), ),
@ -219,7 +219,7 @@ class APRSDPluginProcessPacketThread(APRSDProcessPacketThread):
else: else:
tx.send( tx.send(
packets.MessagePacket( packets.MessagePacket(
from_call=self.config["aprsd"]["callsign"], from_call=CONF.callsign,
to_call=from_call, to_call=from_call,
message_text=subreply, message_text=subreply,
), ),
@ -238,7 +238,7 @@ class APRSDPluginProcessPacketThread(APRSDProcessPacketThread):
LOG.debug(f"Sending '{reply}'") LOG.debug(f"Sending '{reply}'")
tx.send( tx.send(
packets.MessagePacket( packets.MessagePacket(
from_call=self.config["aprsd"]["callsign"], from_call=CONF.callsign,
to_call=from_call, to_call=from_call,
message_text=reply, message_text=reply,
), ),
@ -246,12 +246,12 @@ class APRSDPluginProcessPacketThread(APRSDProcessPacketThread):
# If the message was for us and we didn't have a # If the message was for us and we didn't have a
# response, then we send a usage statement. # response, then we send a usage statement.
if to_call == self.config["aprsd"]["callsign"] and not replied: if to_call == CONF.callsign and not replied:
LOG.warning("Sending help!") LOG.warning("Sending help!")
message_text = "Unknown command! Send 'help' message for help" message_text = "Unknown command! Send 'help' message for help"
tx.send( tx.send(
packets.MessagePacket( packets.MessagePacket(
from_call=self.config["aprsd"]["callsign"], from_call=CONF.callsign,
to_call=from_call, to_call=from_call,
message_text=message_text, message_text=message_text,
), ),
@ -260,11 +260,11 @@ class APRSDPluginProcessPacketThread(APRSDProcessPacketThread):
LOG.error("Plugin failed!!!") LOG.error("Plugin failed!!!")
LOG.exception(ex) LOG.exception(ex)
# Do we need to send a reply? # Do we need to send a reply?
if to_call == self.config["aprsd"]["callsign"]: if to_call == CONF.callsign:
reply = "A Plugin failed! try again?" reply = "A Plugin failed! try again?"
tx.send( tx.send(
packets.MessagePacket( packets.MessagePacket(
from_call=self.config["aprsd"]["callsign"], from_call=CONF.callsign,
to_call=from_call, to_call=from_call,
message_text=reply, message_text=reply,
), ),

View File

@ -1,16 +1,16 @@
import abc
import logging import logging
import os import os
import pathlib import pathlib
import pickle import pickle
from aprsd import config as aprsd_config from oslo_config import cfg
CONF = cfg.CONF
LOG = logging.getLogger("APRSD") LOG = logging.getLogger("APRSD")
class ObjectStoreMixin(metaclass=abc.ABCMeta): class ObjectStoreMixin:
"""Class 'MIXIN' intended to save/load object data. """Class 'MIXIN' intended to save/load object data.
The asumption of how this mixin is used: The asumption of how this mixin is used:
@ -24,13 +24,6 @@ class ObjectStoreMixin(metaclass=abc.ABCMeta):
When APRSD Starts, it calls load() When APRSD Starts, it calls load()
aprsd server -f (flush) will wipe all saved objects. aprsd server -f (flush) will wipe all saved objects.
""" """
@abc.abstractmethod
def is_initialized(self):
"""Return True if the class has been setup correctly.
If this returns False, the ObjectStore doesn't save anything.
"""
def __len__(self): def __len__(self):
return len(self.data) return len(self.data)
@ -44,25 +37,18 @@ class ObjectStoreMixin(metaclass=abc.ABCMeta):
return self.data[id] return self.data[id]
def _init_store(self): def _init_store(self):
if self.is_initialized(): if not CONF.enable_save:
sl = self._save_location() return
if not os.path.exists(sl): sl = CONF.save_location
LOG.warning(f"Save location {sl} doesn't exist") if not os.path.exists(sl):
try: LOG.warning(f"Save location {sl} doesn't exist")
os.makedirs(sl) try:
except Exception as ex: os.makedirs(sl)
LOG.exception(ex) except Exception as ex:
else: LOG.exception(ex)
LOG.warning(f"{self.__class__.__name__} is not initialized")
def _save_location(self):
save_location = self.config.get("aprsd.save_location", None)
if not save_location:
save_location = aprsd_config.DEFAULT_CONFIG_DIR
return save_location
def _save_filename(self): def _save_filename(self):
save_location = self._save_location() save_location = CONF.save_location
return "{}/{}.p".format( return "{}/{}.p".format(
save_location, save_location,
@ -79,45 +65,48 @@ class ObjectStoreMixin(metaclass=abc.ABCMeta):
def save(self): def save(self):
"""Save any queued to disk?""" """Save any queued to disk?"""
if self.is_initialized(): if not CONF.enable_save:
if len(self) > 0: return
LOG.info( if len(self) > 0:
f"{self.__class__.__name__}::Saving" LOG.info(
f" {len(self)} entries to disk at" f"{self.__class__.__name__}::Saving"
f"{self._save_location()}", f" {len(self)} entries to disk at"
) f"{CONF.save_location}",
with open(self._save_filename(), "wb+") as fp: )
pickle.dump(self._dump(), fp) with open(self._save_filename(), "wb+") as fp:
else: pickle.dump(self._dump(), fp)
LOG.debug( else:
"{} Nothing to save, flushing old save file '{}'".format( LOG.debug(
self.__class__.__name__, "{} Nothing to save, flushing old save file '{}'".format(
self._save_filename(), self.__class__.__name__,
), self._save_filename(),
) ),
self.flush() )
self.flush()
def load(self): def load(self):
if self.is_initialized(): if not CONF.enable_save:
if os.path.exists(self._save_filename()): return
try: if os.path.exists(self._save_filename()):
with open(self._save_filename(), "rb") as fp: try:
raw = pickle.load(fp) with open(self._save_filename(), "rb") as fp:
if raw: raw = pickle.load(fp)
self.data = raw if raw:
LOG.debug( self.data = raw
f"{self.__class__.__name__}::Loaded {len(self)} entries from disk.", LOG.debug(
) f"{self.__class__.__name__}::Loaded {len(self)} entries from disk.",
LOG.debug(f"{self.data}") )
except (pickle.UnpicklingError, Exception) as ex: LOG.debug(f"{self.data}")
LOG.error(f"Failed to UnPickle {self._save_filename()}") except (pickle.UnpicklingError, Exception) as ex:
LOG.error(ex) LOG.error(f"Failed to UnPickle {self._save_filename()}")
self.data = {} LOG.error(ex)
self.data = {}
def flush(self): def flush(self):
"""Nuke the old pickle file that stored the old results from last aprsd run.""" """Nuke the old pickle file that stored the old results from last aprsd run."""
if self.is_initialized(): if not CONF.enable_save:
if os.path.exists(self._save_filename()): return
pathlib.Path(self._save_filename()).unlink() if os.path.exists(self._save_filename()):
with self.lock: pathlib.Path(self._save_filename()).unlink()
self.data = {} with self.lock:
self.data = {}

View File

@ -1,12 +1,12 @@
# #
# This file is autogenerated by pip-compile with Python 3.9 # This file is autogenerated by pip-compile with Python 3.10
# by the following command: # by the following command:
# #
# pip-compile --annotation-style=line --resolver=backtracking dev-requirements.in # pip-compile --annotation-style=line --resolver=backtracking dev-requirements.in
# #
add-trailing-comma==2.4.0 # via gray add-trailing-comma==2.4.0 # via gray
alabaster==0.7.12 # via sphinx alabaster==0.7.12 # via sphinx
attrs==22.1.0 # via jsonschema, pytest attrs==22.2.0 # via jsonschema, pytest
autoflake==1.5.3 # via gray autoflake==1.5.3 # via gray
babel==2.11.0 # via sphinx babel==2.11.0 # via sphinx
black==22.12.0 # via gray black==22.12.0 # via gray
@ -20,21 +20,20 @@ click==8.1.3 # via black, pip-tools
colorama==0.4.6 # via tox colorama==0.4.6 # via tox
commonmark==0.9.1 # via rich commonmark==0.9.1 # via rich
configargparse==1.5.3 # via gray configargparse==1.5.3 # via gray
coverage[toml]==6.5.0 # via pytest-cov coverage[toml]==7.0.1 # via pytest-cov
distlib==0.3.6 # via virtualenv distlib==0.3.6 # via virtualenv
docutils==0.19 # via sphinx docutils==0.19 # via sphinx
exceptiongroup==1.0.4 # via pytest exceptiongroup==1.1.0 # via pytest
filelock==3.8.2 # via tox, virtualenv filelock==3.8.2 # via tox, virtualenv
fixit==0.1.4 # via gray fixit==0.1.4 # via gray
flake8==6.0.0 # via -r dev-requirements.in, fixit, pep8-naming flake8==6.0.0 # via -r dev-requirements.in, fixit, pep8-naming
gray==0.13.0 # via -r dev-requirements.in gray==0.13.0 # via -r dev-requirements.in
identify==2.5.9 # via pre-commit identify==2.5.11 # via pre-commit
idna==3.4 # via requests idna==3.4 # via requests
imagesize==1.4.1 # via sphinx imagesize==1.4.1 # via sphinx
importlib-metadata==5.1.0 # via sphinx
importlib-resources==5.10.1 # via fixit importlib-resources==5.10.1 # via fixit
iniconfig==1.1.1 # via pytest iniconfig==1.1.1 # via pytest
isort==5.11.2 # via -r dev-requirements.in, gray isort==5.11.4 # via -r dev-requirements.in, gray
jinja2==3.1.2 # via sphinx jinja2==3.1.2 # via sphinx
jsonschema==4.17.3 # via fixit jsonschema==4.17.3 # via fixit
libcst==0.4.9 # via fixit libcst==0.4.9 # via fixit
@ -46,8 +45,8 @@ nodeenv==1.7.0 # via pre-commit
packaging==22.0 # via build, pyproject-api, pytest, sphinx, tox packaging==22.0 # via build, pyproject-api, pytest, sphinx, tox
pathspec==0.10.3 # via black pathspec==0.10.3 # via black
pep517==0.13.0 # via build pep517==0.13.0 # via build
pep8-naming==0.13.2 # via -r dev-requirements.in pep8-naming==0.13.3 # via -r dev-requirements.in
pip-tools==6.12.0 # via -r dev-requirements.in pip-tools==6.12.1 # via -r dev-requirements.in
platformdirs==2.6.0 # via black, tox, virtualenv platformdirs==2.6.0 # via black, tox, virtualenv
pluggy==1.0.0 # via pytest, tox pluggy==1.0.0 # via pytest, tox
pre-commit==2.20.0 # via -r dev-requirements.in pre-commit==2.20.0 # via -r dev-requirements.in
@ -58,7 +57,7 @@ pyproject-api==1.2.1 # via tox
pyrsistent==0.19.2 # via jsonschema pyrsistent==0.19.2 # via jsonschema
pytest==7.2.0 # via -r dev-requirements.in, pytest-cov pytest==7.2.0 # via -r dev-requirements.in, pytest-cov
pytest-cov==4.0.0 # via -r dev-requirements.in pytest-cov==4.0.0 # via -r dev-requirements.in
pytz==2022.6 # via babel pytz==2022.7 # via babel
pyupgrade==3.3.1 # via gray pyupgrade==3.3.1 # via gray
pyyaml==6.0 # via fixit, libcst, pre-commit pyyaml==6.0 # via fixit, libcst, pre-commit
requests==2.28.1 # via sphinx requests==2.28.1 # via sphinx
@ -74,15 +73,14 @@ sphinxcontrib-serializinghtml==1.1.5 # via sphinx
tokenize-rt==5.0.0 # via add-trailing-comma, pyupgrade tokenize-rt==5.0.0 # via add-trailing-comma, pyupgrade
toml==0.10.2 # via autoflake, pre-commit toml==0.10.2 # via autoflake, pre-commit
tomli==2.0.1 # via black, build, coverage, mypy, pep517, pyproject-api, pytest, tox tomli==2.0.1 # via black, build, coverage, mypy, pep517, pyproject-api, pytest, tox
tox==4.0.9 # via -r dev-requirements.in tox==4.0.16 # via -r dev-requirements.in
typing-extensions==4.4.0 # via black, libcst, mypy, typing-inspect typing-extensions==4.4.0 # via libcst, mypy, typing-inspect
typing-inspect==0.8.0 # via libcst typing-inspect==0.8.0 # via libcst
unify==0.5 # via gray unify==0.5 # via gray
untokenize==0.1.1 # via unify untokenize==0.1.1 # via unify
urllib3==1.26.13 # via requests urllib3==1.26.13 # via requests
virtualenv==20.17.1 # via pre-commit, tox virtualenv==20.17.1 # via pre-commit, tox
wheel==0.38.4 # via pip-tools wheel==0.38.4 # via pip-tools
zipp==3.11.0 # via importlib-metadata, importlib-resources
# The following packages are considered to be unsafe in a requirements file: # The following packages are considered to be unsafe in a requirements file:
# pip # pip

View File

@ -29,3 +29,4 @@ user-agents
pyopenssl pyopenssl
dataclasses dataclasses
dacite2 dacite2
oslo.config

View File

@ -1,15 +1,15 @@
# #
# This file is autogenerated by pip-compile with Python 3.9 # This file is autogenerated by pip-compile with Python 3.10
# by the following command: # by the following command:
# #
# pip-compile --annotation-style=line --resolver=backtracking requirements.in # pip-compile --annotation-style=line --resolver=backtracking requirements.in
# #
aprslib==0.7.2 # via -r requirements.in aprslib==0.7.2 # via -r requirements.in
attrs==22.1.0 # via -r requirements.in, ax253, kiss3 attrs==22.2.0 # via -r requirements.in, ax253, kiss3
ax253==0.1.5.post1 # via kiss3 ax253==0.1.5.post1 # via kiss3
beautifulsoup4==4.11.1 # via -r requirements.in beautifulsoup4==4.11.1 # via -r requirements.in
bidict==0.22.0 # via python-socketio bidict==0.22.0 # via python-socketio
bitarray==2.6.0 # via ax253, kiss3 bitarray==2.6.1 # via ax253, kiss3
certifi==2022.12.7 # via requests certifi==2022.12.7 # via requests
cffi==1.15.1 # via cryptography cffi==1.15.1 # via cryptography
charset-normalizer==2.1.1 # via requests charset-normalizer==2.1.1 # via requests
@ -19,6 +19,7 @@ commonmark==0.9.1 # via rich
cryptography==38.0.4 # via pyopenssl cryptography==38.0.4 # via pyopenssl
dacite2==2.0.0 # via -r requirements.in dacite2==2.0.0 # via -r requirements.in
dataclasses==0.6 # via -r requirements.in dataclasses==0.6 # via -r requirements.in
debtcollector==2.5.0 # via oslo-config
dnspython==2.2.1 # via eventlet dnspython==2.2.1 # via eventlet
eventlet==0.33.2 # via -r requirements.in eventlet==0.33.2 # via -r requirements.in
flask==2.1.2 # via -r requirements.in, flask-classful, flask-httpauth, flask-socketio flask==2.1.2 # via -r requirements.in, flask-classful, flask-httpauth, flask-socketio
@ -28,12 +29,15 @@ flask-socketio==5.3.2 # via -r requirements.in
greenlet==2.0.1 # via eventlet greenlet==2.0.1 # via eventlet
idna==3.4 # via requests idna==3.4 # via requests
imapclient==2.3.1 # via -r requirements.in imapclient==2.3.1 # via -r requirements.in
importlib-metadata==5.1.0 # via ax253, flask, kiss3 importlib-metadata==5.2.0 # via ax253, kiss3
itsdangerous==2.1.2 # via flask itsdangerous==2.1.2 # via flask
jinja2==3.1.2 # via click-completion, flask jinja2==3.1.2 # via click-completion, flask
kiss3==8.0.0 # via -r requirements.in kiss3==8.0.0 # via -r requirements.in
markupsafe==2.1.1 # via jinja2 markupsafe==2.1.1 # via jinja2
pbr==5.11.0 # via -r requirements.in netaddr==0.8.0 # via oslo-config
oslo-config==9.0.0 # via -r requirements.in
oslo-i18n==5.1.0 # via oslo-config
pbr==5.11.0 # via -r requirements.in, oslo-i18n, stevedore
pluggy==1.0.0 # via -r requirements.in pluggy==1.0.0 # via -r requirements.in
pycparser==2.21 # via cffi pycparser==2.21 # via cffi
pygments==2.13.0 # via rich pygments==2.13.0 # via rich
@ -42,13 +46,15 @@ pyserial==3.5 # via pyserial-asyncio
pyserial-asyncio==0.6 # via kiss3 pyserial-asyncio==0.6 # via kiss3
python-engineio==4.3.4 # via python-socketio python-engineio==4.3.4 # via python-socketio
python-socketio==5.7.2 # via flask-socketio python-socketio==5.7.2 # via flask-socketio
pytz==2022.6 # via -r requirements.in pytz==2022.7 # via -r requirements.in
pyyaml==6.0 # via -r requirements.in pyyaml==6.0 # via -r requirements.in, oslo-config
requests==2.28.1 # via -r requirements.in, update-checker requests==2.28.1 # via -r requirements.in, oslo-config, update-checker
rfc3986==2.0.0 # via oslo-config
rich==12.6.0 # via -r requirements.in rich==12.6.0 # via -r requirements.in
shellingham==1.5.0 # via click-completion shellingham==1.5.0 # via click-completion
six==1.16.0 # via -r requirements.in, click-completion, eventlet, imapclient six==1.16.0 # via -r requirements.in, click-completion, eventlet, imapclient
soupsieve==2.3.2.post1 # via beautifulsoup4 soupsieve==2.3.2.post1 # via beautifulsoup4
stevedore==4.1.1 # via oslo-config
tabulate==0.9.0 # via -r requirements.in tabulate==0.9.0 # via -r requirements.in
thesmuggler==1.0.1 # via -r requirements.in thesmuggler==1.0.1 # via -r requirements.in
ua-parser==0.16.1 # via user-agents ua-parser==0.16.1 # via user-agents
@ -56,5 +62,5 @@ update-checker==0.18.0 # via -r requirements.in
urllib3==1.26.13 # via requests urllib3==1.26.13 # via requests
user-agents==2.2.0 # via -r requirements.in user-agents==2.2.0 # via -r requirements.in
werkzeug==2.1.2 # via -r requirements.in, flask werkzeug==2.1.2 # via -r requirements.in, flask
wrapt==1.14.1 # via -r requirements.in wrapt==1.14.1 # via -r requirements.in, debtcollector
zipp==3.11.0 # via importlib-metadata zipp==3.11.0 # via importlib-metadata

View File

@ -34,6 +34,10 @@ packages =
[entry_points] [entry_points]
console_scripts = console_scripts =
aprsd = aprsd.aprsd:main aprsd = aprsd.aprsd:main
oslo.config.opts =
aprsd.conf = aprsd.conf.opts:list_opts
oslo.config.opts.defaults =
aprsd.conf = aprsd.conf:set_lib_defaults
[build_sphinx] [build_sphinx]
source-dir = docs source-dir = docs

View File

@ -5,21 +5,20 @@ from aprsd.plugins import email
class TestEmail(unittest.TestCase): class TestEmail(unittest.TestCase):
def test_get_email_from_shortcut(self): def test_get_email_from_shortcut(self):
config = {"aprsd": {"email": {"shortcuts": {}}}}
email_address = "something@something.com" email_address = "something@something.com"
addr = f"-{email_address}" addr = f"-{email_address}"
actual = email.get_email_from_shortcut(config, addr) actual = email.get_email_from_shortcut(addr)
self.assertEqual(addr, actual) self.assertEqual(addr, actual)
config = {"aprsd": {"email": {"nothing": "nothing"}}} config = {"aprsd": {"email": {"nothing": "nothing"}}}
actual = email.get_email_from_shortcut(config, addr) actual = email.get_email_from_shortcut(addr)
self.assertEqual(addr, actual) self.assertEqual(addr, actual)
config = {"aprsd": {"email": {"shortcuts": {"not_used": "empty"}}}} config = {"aprsd": {"email": {"shortcuts": {"not_used": "empty"}}}}
actual = email.get_email_from_shortcut(config, addr) actual = email.get_email_from_shortcut(addr)
self.assertEqual(addr, actual) self.assertEqual(addr, actual)
config = {"aprsd": {"email": {"shortcuts": {"-wb": email_address}}}} config = {"aprsd": {"email": {"shortcuts": {"-wb": email_address}}}}
short = "-wb" short = "-wb"
actual = email.get_email_from_shortcut(config, short) actual = email.get_email_from_shortcut(short)
self.assertEqual(email_address, actual) self.assertEqual(email_address, actual)

View File

@ -17,6 +17,6 @@ class TestMain(unittest.TestCase):
"""Test to make sure we fail.""" """Test to make sure we fail."""
imap_mock.return_value = None imap_mock.return_value = None
smtp_mock.return_value = {"smaiof": "fire"} smtp_mock.return_value = {"smaiof": "fire"}
config = mock.MagicMock() mock.MagicMock()
email.validate_email_config(config, True) email.validate_email_config(True)