1
0
mirror of https://github.com/craigerl/aprsd.git synced 2026-08-16 16:43:52 -04:00

Compare commits

..

7 Commits

Author SHA1 Message Date
hemna bda2ef00dd Fix admin logging tab 2021-11-12 12:17:45 -05:00
hemna 446484e631 Added new list-plugins command
This patch adds the new list-plugins command that shows the
list of built in plugins for APRSD.
2021-11-12 11:36:22 -05:00
hemna a8a6b1aa07 Don't require check-version command to have a config
This patch removes the need for check-version to have a
config file.
2021-11-12 10:23:27 -05:00
hemna 8842fb1b44 Healthcheck command doesn't need the aprsd.yml config
This patch updates the healthcheck command to not require
the aprsd.yml config file to exist.   The healthcheck
calls a running aprsd, collects the stats to determine if it's
healthy.
2021-11-10 11:52:51 -05:00
hemna 152132b0ed Fix test failures 2021-11-10 11:51:21 -05:00
hemna 7787dc1be4 Removed requirement for aprs.fi key
This removed the requirement of running APRSD for specifying
the aprs.fi key in the config file.  The plugins that need the
key have been updated to set enabled = False when the key is missing.
2021-11-10 11:01:10 -05:00
hemna 10e34d8634 Updated Changelog 2021-11-09 15:06:40 -05:00
21 changed files with 180 additions and 63 deletions
+17
View File
@@ -1,9 +1,26 @@
CHANGES
=======
v2.5.2
------
* Added new list-plugins command
* Don't require check-version command to have a config
* Healthcheck command doesn't need the aprsd.yml config
* Fix test failures
* Removed requirement for aprs.fi key
* Updated Changelog
v2.5.1
------
* Removed stock plugin
* Removed the stock plugin
v2.5.0
------
* Updated for v2.5.0
* Updated Dockerfile's and build script for docker
* Cleaned up some verbose output & colorized output
* Reworked all the common arguments
+3 -2
View File
@@ -67,7 +67,8 @@ def main():
# First import all the possible commands for the CLI
# The commands themselves live in the cmds directory
from .cmds import ( # noqa
completion, dev, healthcheck, listen, send_message, server,
completion, dev, healthcheck, list_plugins, listen, send_message,
server,
)
cli()
@@ -97,7 +98,7 @@ def signal_handler(sig, frame):
@cli.command()
@cli_helper.add_options(cli_helper.common_options)
@click.pass_context
@cli_helper.process_standard_options
@cli_helper.process_standard_options_no_config
def check_version(ctx):
"""Check this version against the latest in pypi.org."""
level, msg = utils._check_version()
+21
View File
@@ -65,3 +65,24 @@ def process_standard_options(f: F) -> F:
return f(*args, **kwargs)
return update_wrapper(t.cast(F, new_func), f)
def process_standard_options_no_config(f: F) -> F:
"""Use this as a decorator when config isn't needed."""
def new_func(*args, **kwargs):
ctx = args[0]
ctx.ensure_object(dict)
ctx.obj["loglevel"] = kwargs["loglevel"]
ctx.obj["config_file"] = kwargs["config_file"]
ctx.obj["quiet"] = kwargs["quiet"]
log.setup_logging_no_config(
ctx.obj["loglevel"],
ctx.obj["quiet"],
)
del kwargs["loglevel"]
del kwargs["config_file"]
del kwargs["quiet"]
return f(*args, **kwargs)
return update_wrapper(t.cast(F, new_func), f)
+1 -2
View File
@@ -40,10 +40,9 @@ LOG = logging.getLogger("APRSD")
help="How long to wait for healtcheck url to come back",
)
@click.pass_context
@cli_helper.process_standard_options
@cli_helper.process_standard_options_no_config
def healthcheck(ctx, health_url, timeout):
"""Check the health of the running aprsd server."""
ctx.obj["config"]
LOG.debug(f"APRSD HealthCheck version: {aprsd.__version__}")
try:
+59
View File
@@ -0,0 +1,59 @@
import inspect
import logging
from textwrap import indent
import click
from tabulate import tabulate
from aprsd import cli_helper, plugin
from aprsd.plugins import (
email, fortune, location, notify, ping, query, time, version, weather,
)
from ..aprsd import cli
LOG = logging.getLogger("APRSD")
@cli.command()
@cli_helper.add_options(cli_helper.common_options)
@click.pass_context
@cli_helper.process_standard_options_no_config
def list_plugins(ctx):
"""List the built in plugins available to APRSD."""
modules = [email, fortune, location, notify, ping, query, time, version, weather]
plugins = []
for module in modules:
entries = inspect.getmembers(module, inspect.isclass)
for entry in entries:
cls = entry[1]
if issubclass(cls, plugin.APRSDPluginBase):
info = {
"name": cls.__qualname__,
"path": f"{cls.__module__}.{cls.__qualname__}",
"version": cls.version,
"docstring": cls.__doc__,
"short_desc": cls.short_description,
}
if issubclass(cls, plugin.APRSDRegexCommandPluginBase):
info["command_regex"] = cls.command_regex
info["type"] = "RegexCommand"
if issubclass(cls, plugin.APRSDWatchListPluginBase):
info["type"] = "WatchList"
plugins.append(info)
lines = []
headers = ("Plugin Name", "Plugin Path", "Type", "Info")
for entry in plugins:
lines.append(
(entry["name"], entry["path"], entry["type"], entry["short_desc"]),
)
click.echo(indent(tabulate(lines, headers, disable_numparse=True), " "))
-6
View File
@@ -327,12 +327,6 @@ def parse_config(config_file):
"ham.callsign",
default_fail=DEFAULT_CONFIG_DICT["ham"]["callsign"],
)
check_option(
config,
["services", "aprs.fi", "apiKey"],
default_fail=DEFAULT_CONFIG_DICT["services"]["aprs.fi"]["apiKey"],
)
check_option(
config,
"aprs.login",
+2 -2
View File
@@ -19,7 +19,7 @@ from werkzeug.security import check_password_hash, generate_password_hash
import aprsd
from aprsd import client
from aprsd import config as aprsd_config
from aprsd import messaging, packets, plugin, stats, threads, utils
from aprsd import log, messaging, packets, plugin, stats, threads, utils
from aprsd.clients import aprsis
@@ -500,7 +500,7 @@ class LogMonitorThread(threads.APRSDThread):
def loop(self):
global socketio
try:
record = threads.logging_queue.get(block=True, timeout=5)
record = log.logging_queue.get(block=True, timeout=5)
json_record = self.json_record(record)
socketio.emit(
"log_entry", json_record,
+17
View File
@@ -51,3 +51,20 @@ def setup_logging(config, loglevel, quiet):
LOG.addHandler(sh)
if imap_logger:
imap_logger.addHandler(sh)
def setup_logging_no_config(loglevel, quiet):
log_level = aprsd_config.LOG_LEVELS[loglevel]
LOG.setLevel(log_level)
log_format = aprsd_config.DEFAULT_LOG_FORMAT
date_format = aprsd_config.DEFAULT_DATE_FORMAT
log_formatter = logging.Formatter(fmt=log_format, datefmt=date_format)
fh = NullHandler()
fh.setFormatter(log_formatter)
LOG.addHandler(fh)
if not quiet:
sh = logging.StreamHandler(sys.stdout)
sh.setFormatter(log_formatter)
LOG.addHandler(sh)
+16 -2
View File
@@ -12,6 +12,7 @@ import threading
import pluggy
from thesmuggler import smuggle
import aprsd
from aprsd import client, messaging, packets, threads
@@ -51,7 +52,7 @@ class APRSDPluginBase(metaclass=abc.ABCMeta):
config = None
rx_count = 0
tx_count = 0
version = "1.0"
version = aprsd.__version__
# Holds the list of APRSDThreads that the plugin creates
threads = []
@@ -241,11 +242,24 @@ class APRSDRegexCommandPluginBase(APRSDPluginBase, metaclass=abc.ABCMeta):
if result:
self.tx_inc()
else:
LOG.warning(f"{self.__class__} isn't enabled.")
result = f"{self.__class__.__name__} isn't enabled"
LOG.warning(result)
return result
class APRSFIKEYMixin:
"""Mixin class to enable checking the existence of the aprs.fi apiKey."""
def ensure_aprs_fi_key(self):
try:
self.config.check_option(["services", "aprs.fi", "apiKey"])
self.enabled = True
except Exception as ex:
LOG.error(f"Failed to find config aprs.fi:apikey {ex}")
self.enabled = False
class HelpPlugin(APRSDRegexCommandPluginBase):
"""Help Plugin that is always enabled.
+1 -1
View File
@@ -59,9 +59,9 @@ class EmailInfo:
class EmailPlugin(plugin.APRSDRegexCommandPluginBase):
"""Email Plugin."""
version = "1.0"
command_regex = "^-.*"
command_name = "email"
short_description = "Send and Receive email"
# message_number:time combos so we don't resend the same email in
# five mins {int:int}
+11 -7
View File
@@ -11,9 +11,18 @@ LOG = logging.getLogger("APRSD")
class FortunePlugin(plugin.APRSDRegexCommandPluginBase):
"""Fortune."""
version = "1.0"
command_regex = "^[fF]"
command_name = "fortune"
short_description = "Give me a fortune"
fortune_path = None
def setup(self):
self.fortune_path = shutil.which("fortune")
if not self.fortune_path:
self.enabled = False
else:
self.enabled = True
@trace.trace
def process(self, packet):
@@ -25,13 +34,8 @@ class FortunePlugin(plugin.APRSDRegexCommandPluginBase):
reply = None
fortune_path = shutil.which("fortune")
if not fortune_path:
reply = "Fortune command not installed"
return reply
try:
cmnd = [fortune_path, "-s", "-n 60"]
cmnd = [self.fortune_path, "-s", "-n 60"]
command = " ".join(cmnd)
output = subprocess.check_output(
command,
+5 -9
View File
@@ -8,12 +8,15 @@ from aprsd import plugin, plugin_utils, trace
LOG = logging.getLogger("APRSD")
class LocationPlugin(plugin.APRSDRegexCommandPluginBase):
class LocationPlugin(plugin.APRSDRegexCommandPluginBase, plugin.APRSFIKEYMixin):
"""Location!"""
version = "1.0"
command_regex = "^[lL]"
command_name = "location"
short_description = "Where in the world is a CALLSIGN's last GPS beacon?"
def setup(self):
self.ensure_aprs_fi_key()
@trace.trace
def process(self, packet):
@@ -22,13 +25,6 @@ class LocationPlugin(plugin.APRSDRegexCommandPluginBase):
message = packet.get("message_text", None)
# ack = packet.get("msgNo", "0")
# get last location of a callsign, get descriptive name from weather service
try:
self.config.check_option(["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"]
# optional second argument is a callsign to search
+1 -1
View File
@@ -15,7 +15,7 @@ class NotifySeenPlugin(plugin.APRSDWatchListPluginBase):
seen was older than the configured age limit.
"""
version = "1.0"
short_description = "Notify me when a CALLSIGN is recently seen on APRS-IS"
def process(self, packet):
LOG.info("NotifySeenPlugin")
+1 -1
View File
@@ -10,9 +10,9 @@ LOG = logging.getLogger("APRSD")
class PingPlugin(plugin.APRSDRegexCommandPluginBase):
"""Ping."""
version = "1.0"
command_regex = "^[pP]"
command_name = "ping"
short_description = "reply with a Pong!"
@trace.trace
def process(self, packet):
+1 -1
View File
@@ -11,9 +11,9 @@ LOG = logging.getLogger("APRSD")
class QueryPlugin(plugin.APRSDRegexCommandPluginBase):
"""Query command."""
version = "1.0"
command_regex = r"^\!.*"
command_name = "query"
short_description = "APRSD Owner command to query messages in the MsgTrack"
@trace.trace
def process(self, packet):
+11 -19
View File
@@ -14,9 +14,9 @@ LOG = logging.getLogger("APRSD")
class TimePlugin(plugin.APRSDRegexCommandPluginBase):
"""Time command."""
version = "1.0"
command_regex = "^[tT]"
command_name = "time"
short_description = "What is the current local time."
def _get_local_tz(self):
return pytz.timezone(time.strftime("%Z"))
@@ -49,12 +49,15 @@ class TimePlugin(plugin.APRSDRegexCommandPluginBase):
return self.build_date_str(localzone)
class TimeOpenCageDataPlugin(TimePlugin):
class TimeOpenCageDataPlugin(TimePlugin, plugin.APRSFIKEYMixin):
"""geocage based timezone fetching."""
version = "1.0"
command_regex = "^[tT]"
command_name = "time"
short_description = "Current time of GPS beacon timezone. Uses OpenCage"
def setup(self):
self.ensure_aprs_fi_key()
@trace.trace
def process(self, packet):
@@ -62,13 +65,6 @@ class TimeOpenCageDataPlugin(TimePlugin):
message = packet.get("message_text", None)
# ack = packet.get("msgNo", "0")
# get last location of a callsign, get descriptive name from weather service
try:
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"]
# optional second argument is a callsign to search
@@ -115,12 +111,15 @@ class TimeOpenCageDataPlugin(TimePlugin):
return self.build_date_str(localzone)
class TimeOWMPlugin(TimePlugin):
class TimeOWMPlugin(TimePlugin, plugin.APRSFIKEYMixin):
"""OpenWeatherMap based timezone fetching."""
version = "1.0"
command_regex = "^[tT]"
command_name = "time"
short_description = "Current time of GPS beacon's timezone. Uses OpenWeatherMap"
def setup(self):
self.ensure_aprs_fi_key()
@trace.trace
def process(self, packet):
@@ -128,13 +127,6 @@ class TimeOWMPlugin(TimePlugin):
message = packet.get("message_text", None)
# ack = packet.get("msgNo", "0")
# get last location of a callsign, get descriptive name from weather service
try:
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"
# optional second argument is a callsign to search
a = re.search(r"^.*\s+(.*)", message)
if a is not None:
+1 -1
View File
@@ -10,9 +10,9 @@ LOG = logging.getLogger("APRSD")
class VersionPlugin(plugin.APRSDRegexCommandPluginBase):
"""Version of APRSD Plugin."""
version = "1.0"
command_regex = "^[vV]"
command_name = "version"
short_description = "What is the APRSD Version"
# message_number:time combos so we don't resend the same email in
# five mins {int:int}
+4 -4
View File
@@ -23,9 +23,9 @@ class USWeatherPlugin(plugin.APRSDRegexCommandPluginBase):
"weather" - returns weather near the calling callsign
"""
version = "1.0"
command_regex = "^[wW]"
command_name = "USWeather"
short_description = "Provide USA only weather of GPS Beacon location"
@trace.trace
def process(self, packet):
@@ -86,9 +86,9 @@ class USMetarPlugin(plugin.APRSDRegexCommandPluginBase):
"""
version = "1.0"
command_regex = "^[metar]"
command_name = "USMetar"
short_description = "USA only METAR of GPS Beacon location"
@trace.trace
def process(self, packet):
@@ -178,9 +178,9 @@ class OWMWeatherPlugin(plugin.APRSDRegexCommandPluginBase):
"""
version = "1.0"
command_regex = "^[wW]"
command_name = "OpenWeatherMap"
short_description = "OpenWeatherMap weather of GPS Beacon location"
def help(self):
_help = [
@@ -308,9 +308,9 @@ class AVWXWeatherPlugin(plugin.APRSDRegexCommandPluginBase):
docker build -f Dockerfile -t avwx-api:master .
"""
version = "1.0"
command_regex = "^[mM]"
command_name = "AVWXWeather"
short_description = "AVWX weather of GPS Beacon location"
def help(self):
_help = [
+1
View File
@@ -20,3 +20,4 @@ thesmuggler
update_checker
flask-socketio
eventlet
tabulate
+2
View File
@@ -104,6 +104,8 @@ six==1.16.0
# imapclient
# pyopenssl
# signalslot
tabulate==0.8.9
# via -r requirements.in
thesmuggler==1.0.1
# via -r requirements.in
update-checker==0.18.0
+5 -5
View File
@@ -22,6 +22,7 @@ class TestPlugin(unittest.TestCase):
self.config = config.DEFAULT_CONFIG_DICT
self.config["ham"]["callsign"] = self.fromcall
self.config["aprs"]["login"] = fake.FAKE_TO_CALLSIGN
self.config["services"]["aprs.fi"]["apiKey"] = "something"
# Inintialize the stats object with the config
stats.APRSDStats(self.config)
packets.WatchList(config=self.config)
@@ -127,9 +128,9 @@ class TestPlugin(unittest.TestCase):
class TestFortunePlugin(TestPlugin):
@mock.patch("shutil.which")
def test_fortune_fail(self, mock_which):
fortune = fortune_plugin.FortunePlugin(self.config)
mock_which.return_value = None
expected = "Fortune command not installed"
fortune = fortune_plugin.FortunePlugin(self.config)
expected = "FortunePlugin isn't enabled"
packet = fake.fake_packet(message="fortune")
actual = fortune.filter(packet)
self.assertEqual(expected, actual)
@@ -137,10 +138,9 @@ class TestFortunePlugin(TestPlugin):
@mock.patch("subprocess.check_output")
@mock.patch("shutil.which")
def test_fortune_success(self, mock_which, mock_output):
fortune = fortune_plugin.FortunePlugin(self.config)
mock_which.return_value = "/usr/bin/games"
mock_which.return_value = "/usr/bin/games/fortune"
mock_output.return_value = "Funny fortune"
fortune = fortune_plugin.FortunePlugin(self.config)
expected = "Funny fortune"
packet = fake.fake_packet(message="fortune")