diff --git a/aprsd_slack_plugin/base_plugin.py b/aprsd_slack_plugin/base_plugin.py index ca1b74c..4677fc7 100644 --- a/aprsd_slack_plugin/base_plugin.py +++ b/aprsd_slack_plugin/base_plugin.py @@ -1,9 +1,12 @@ import logging +from oslo_config import cfg from slack_sdk import WebClient import aprsd_slack_plugin + +CONF = cfg.CONF LOG = logging.getLogger("APRSD") @@ -39,33 +42,29 @@ class SlackPluginBase: """ version = aprsd_slack_plugin.__version__ + swc = None + slack_channels = None def setup_slack(self): """Create the slack require client from config.""" - # signing_secret = self.config["slack"]["signing_secret"] - try: - self.config.exists(["services", "slack", "bot_token"]) - except Exception as ex: - LOG.error("Failed to find config slack:bot_token {}".format(ex)) - return "No slack bot_token found" + if not CONF.aprsd_slack_plugin.signing_secret: + LOG.error("Failed to find config aprsd_slack_plugin.signing_secret") + return "No slack signing_secret found" - bot_token = self.config["services"]["slack"]["bot_token"] - if not bot_token: + if not CONF.aprsd_slack_plugin.bot_token: LOG.error( - "APRSD config is missing slack: bot_token:. " + "APRSD config is missing aprsd_slack_plugin.bot_token. " "Please install the slack app and get the " "Bot User OAth Access Token.", ) return False - self.swc = WebClient(token=bot_token) - self.slack_channels = self.config["services"]["slack"].get("channels", None) - if not self.slack_channels: - LOG.error( - "APRSD config is missing slack: channels: " - "Please add a slack channel name to send messages.", - ) + if not CONF.aprsd_slack_plugin.channels: + LOG.error("aprsd_slack_plugin.channels is missing") return False + self.swc = WebClient(token=CONF.aprsd_slack_plugin.bot_token) + self.slack_channels = CONF.aprsd_slack_plugin.channels + return True diff --git a/aprsd_slack_plugin/conf/__init__.py b/aprsd_slack_plugin/conf/__init__.py new file mode 100644 index 0000000..82e9a76 --- /dev/null +++ b/aprsd_slack_plugin/conf/__init__.py @@ -0,0 +1,10 @@ +import logging + +from oslo_config import cfg + +from aprsd_slack_plugin.conf import slack + + +CONF = cfg.CONF + +slack.register_opts(CONF) diff --git a/aprsd_slack_plugin/conf/opts.py b/aprsd_slack_plugin/conf/opts.py new file mode 100644 index 0000000..a7549d5 --- /dev/null +++ b/aprsd_slack_plugin/conf/opts.py @@ -0,0 +1,80 @@ +# Copyright 2015 OpenStack Foundation +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +""" +This is the single point of entry to generate the sample configuration +file for Nova. It collects all the necessary info from the other modules +in this package. It is assumed that: + +* every other module in this package has a 'list_opts' function which + return a dict where + * the keys are strings which are the group names + * the value of each key is a list of config options for that group +* the nova.conf package doesn't have further packages with config options +* this module is only used in the context of sample file generation +""" + +import collections +import importlib +import os +import pkgutil + + +LIST_OPTS_FUNC_NAME = "list_opts" + + +def _tupleize(dct): + """Take the dict of options and convert to the 2-tuple format.""" + return [(key, val) for key, val in dct.items()] + + +def list_opts(): + opts = collections.defaultdict(list) + module_names = _list_module_names() + imported_modules = _import_modules(module_names) + _append_config_options(imported_modules, opts) + return _tupleize(opts) + + +def _list_module_names(): + module_names = [] + package_path = os.path.dirname(os.path.abspath(__file__)) + for _, modname, ispkg in pkgutil.iter_modules(path=[package_path]): + if modname == "opts" or ispkg: + continue + else: + module_names.append(modname) + return module_names + + +def _import_modules(module_names): + imported_modules = [] + for modname in module_names: + mod = importlib.import_module("aprsd_slack_plugin.conf." + modname) + if not hasattr(mod, LIST_OPTS_FUNC_NAME): + msg = "The module 'aprsd_slack_plugin.conf.%s' should have a '%s' "\ + "function which returns the config options." % \ + (modname, LIST_OPTS_FUNC_NAME) + raise Exception(msg) + else: + imported_modules.append(mod) + return imported_modules + + +def _append_config_options(imported_modules, config_options): + for mod in imported_modules: + configs = mod.list_opts() + for key, val in configs.items(): + config_options[key].extend(val) diff --git a/aprsd_slack_plugin/conf/slack.py b/aprsd_slack_plugin/conf/slack.py new file mode 100644 index 0000000..5d67735 --- /dev/null +++ b/aprsd_slack_plugin/conf/slack.py @@ -0,0 +1,43 @@ +from oslo_config import cfg + + +slack_group = cfg.OptGroup( + name="aprsd_slack_plugin", + title="APRSD Slack Plugin settings", +) + +slack_opts = [ + cfg.StrOpt( + "signing_secret", + default=None, + help="Your Slack account signing secret" + "You have to create a slack bot account first. " + "https://api.slack.com/start/building/bolt-python", + ), + cfg.StrOpt( + "bot_token", + default=None, + help="Your Slack bot's token", + ), + cfg.ListOpt( + "channels", + default=None, + help="The channels you want messages sent to. This is a CSV list" + "of slack channel names.", + ), +] + +ALL_OPTS = ( + slack_opts +) + + +def register_opts(cfg): + cfg.register_group(slack_group) + cfg.register_opts(ALL_OPTS, group=slack_group) + + +def list_opts(): + return { + slack_group.name: slack_opts, + } diff --git a/aprsd_slack_plugin/location_plugin.py b/aprsd_slack_plugin/location_plugin.py index 06a39b3..38da7ad 100644 --- a/aprsd_slack_plugin/location_plugin.py +++ b/aprsd_slack_plugin/location_plugin.py @@ -3,15 +3,21 @@ import re import time from aprsd import packets, plugin, plugin_utils +from oslo_config import cfg import aprsd_slack_plugin from aprsd_slack_plugin import base_plugin +CONF = cfg.CONF LOG = logging.getLogger("APRSD") -class SlackLocationPlugin(base_plugin.SlackPluginBase, plugin.APRSDRegexCommandPluginBase): +class SlackLocationPlugin( + base_plugin.SlackPluginBase, + plugin.APRSDRegexCommandPluginBase, + plugin.APRSFIKEYMixin, +): """SlackCommandPlugin. This APRSD plugin looks for the location command comming in @@ -35,7 +41,7 @@ class SlackLocationPlugin(base_plugin.SlackPluginBase, plugin.APRSDRegexCommandP Install the app/bot into your workspace. - Edit your ~/.config/aprsd/aprsd.yml and add the section + Edit your ~/.config/aprsd/aprsd.conf and add the section slack: signing_secret: bot_token: @@ -48,24 +54,21 @@ class SlackLocationPlugin(base_plugin.SlackPluginBase, plugin.APRSDRegexCommandP command_regex = "^[lL]" command_name = "location-slack" + def setup(self): + self.ensure_aprs_fi_key() + if self.enabled: + config_set = self.setup_slack() + if not config_set: + self.enabled = False + def process(self, packet): LOG.info("SlackCommandPlugin") fromcall = packet.from_call message = packet.message_text - is_setup = self.setup_slack() - if not is_setup: - return - # 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"] + api_key = CONF.aprs_fi.apiKey # optional second argument is a callsign to search a = re.search(r"^.*\s+(.*)", message) diff --git a/aprsd_slack_plugin/message_plugin.py b/aprsd_slack_plugin/message_plugin.py index 403029e..5f4f1dc 100644 --- a/aprsd_slack_plugin/message_plugin.py +++ b/aprsd_slack_plugin/message_plugin.py @@ -2,11 +2,13 @@ import logging import re from aprsd import packets +from oslo_config import cfg import aprsd_slack_plugin from aprsd_slack_plugin import base_plugin +CONF = cfg.CONF LOG = logging.getLogger("APRSD") @@ -46,19 +48,20 @@ class SlackMessagePlugin(base_plugin.SlackPluginBase): command_regex = "^[sS]" command_name = "message-slack" + def setup(self): + config_set = self.setup_slack() + if not config_set: + self.enabled = False + else: + self.enabled = True + def command(self, packet): message = packet.message_text fromcall = packet.from_call LOG.info(f"SlackMessagePlugin '{message}'") - is_setup = self.setup_slack() - if not is_setup: - LOG.error("Slack isn't setup!") - return - # optional second argument is a callsign to search a = re.search(r"^.*\s+(.*)", message) - LOG.debug(a) if a is not None: searchcall = a.group(1) searchcall = searchcall.upper() diff --git a/aprsd_slack_plugin/notify_plugin.py b/aprsd_slack_plugin/notify_plugin.py index e9a43e3..cfe81bd 100644 --- a/aprsd_slack_plugin/notify_plugin.py +++ b/aprsd_slack_plugin/notify_plugin.py @@ -1,10 +1,13 @@ import logging from aprsd import messaging, packets, plugin +from oslo_config import cfg import aprsd_slack_plugin from aprsd_slack_plugin import base_plugin + +CONF = cfg.CONF LOG = logging.getLogger("APRSD") @@ -16,10 +19,17 @@ class SlackNotifyPlugin( version = aprsd_slack_plugin.__version__ + def setup(self): + config_set = self.setup_slack() + if not config_set: + self.enabled = False + else: + self.enabled = True + def process(self, packet): LOG.info("SlackCommandPlugin") - fromcall = packet["from"] + fromcall = packet.from_call # message = packet["message_text"] is_setup = self.setup_slack() @@ -29,13 +39,13 @@ class SlackNotifyPlugin( wl = packets.WatchList() if wl.is_old(packet["from"]): # get last location of a callsign, get descriptive name from weather service - callsign_url = "".format(fromcall, fromcall) + callsign_url = f"" message = {} message["username"] = "APRSD - Slack Notification Plugin" message["icon_emoji"] = ":satellite_antenna:" message["attachments"] = [{}] - message["text"] = "{} - Is now on APRS".format(callsign_url) + message["text"] = f"{callsign_url} - Is now on APRS" message["channel"] = "#hemna" LOG.debug(message) diff --git a/requirements-dev.txt b/requirements-dev.txt index 065c0a1..e69de29 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,111 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.9 -# by the following command: -# -# pip-compile --annotation-style=line --resolver=backtracking requirements-dev.in -# -alabaster==0.7.12 # via sphinx -aprsd==2.6.1 # via -r requirements-dev.in -aprslib==0.7.2 # via aprsd -attrs==22.1.0 # via aprsd, ax253, kiss3, pytest -ax253==0.1.5.post1 # via aprsd, kiss3 -babel==2.11.0 # via sphinx -beautifulsoup4==4.11.1 # via aprsd -bidict==0.22.0 # via aprsd, python-socketio -bitarray==2.6.0 # via aprsd, ax253, kiss3 -black==22.12.0 # via -r requirements-dev.in -build==0.9.0 # via pip-tools -cachetools==5.2.0 # via tox -certifi==2022.12.7 # via aprsd, requests -cffi==1.15.1 # via aprsd, cryptography -cfgv==3.3.1 # via pre-commit -chardet==5.1.0 # via tox -charset-normalizer==2.1.1 # via aprsd, requests -click==8.1.3 # via aprsd, black, click-completion, flask, pip-tools -click-completion==0.5.2 # via aprsd -colorama==0.4.6 # via tox -commonmark==0.9.1 # via aprsd, rich -coverage[toml]==6.5.0 # via pytest-cov -cryptography==38.0.4 # via aprsd, pyopenssl -distlib==0.3.6 # via virtualenv -dnspython==2.2.1 # via aprsd, eventlet -docutils==0.19 # via sphinx -eventlet==0.33.2 # via aprsd -exceptiongroup==1.0.4 # via pytest -filelock==3.8.2 # via tox, virtualenv -flake8==6.0.0 # via -r requirements-dev.in, pep8-naming -flask==2.1.2 # via aprsd, flask-classful, flask-httpauth, flask-socketio -flask-classful==0.14.2 # via aprsd -flask-httpauth==4.7.0 # via aprsd -flask-socketio==5.3.2 # via aprsd -greenlet==2.0.1 # via aprsd, eventlet -identify==2.5.10 # via pre-commit -idna==3.4 # via aprsd, requests -imagesize==1.4.1 # via sphinx -imapclient==2.3.1 # via aprsd -importlib-metadata==5.1.0 # via aprsd, ax253, flask, kiss3, sphinx -iniconfig==1.1.1 # via pytest -isort==5.11.3 # via -r requirements-dev.in -itsdangerous==2.1.2 # via aprsd, flask -jinja2==3.1.2 # via aprsd, click-completion, flask, sphinx -kiss3==8.0.0 # via aprsd -markupsafe==2.1.1 # via aprsd, jinja2 -mccabe==0.7.0 # via flake8 -mypy==0.991 # via -r requirements-dev.in -mypy-extensions==0.4.3 # via black, mypy -nodeenv==1.7.0 # via pre-commit -packaging==22.0 # via build, pyproject-api, pytest, sphinx, tox -pathspec==0.10.3 # via black -pbr==5.11.0 # via -r requirements-dev.in, aprsd -pep517==0.13.0 # via build -pep8-naming==0.13.2 # via -r requirements-dev.in -pip-tools==6.12.1 # via -r requirements-dev.in -platformdirs==2.6.0 # via black, tox, virtualenv -pluggy==1.0.0 # via aprsd, pytest, tox -pre-commit==2.20.0 # via -r requirements-dev.in -pycodestyle==2.10.0 # via flake8 -pycparser==2.21 # via aprsd, cffi -pyflakes==3.0.1 # via flake8 -pygments==2.13.0 # via aprsd, rich, sphinx -pyopenssl==22.1.0 # via aprsd -pyproject-api==1.2.1 # via tox -pyserial==3.5 # via aprsd, pyserial-asyncio -pyserial-asyncio==0.6 # via aprsd, kiss3 -pytest==7.2.0 # via -r requirements-dev.in, pytest-cov -pytest-cov==4.0.0 # via -r requirements-dev.in -python-engineio==4.3.4 # via aprsd, python-socketio -python-socketio==5.7.2 # via aprsd, flask-socketio -pytz==2022.6 # via aprsd, babel -pyyaml==6.0 # via aprsd, pre-commit -requests==2.28.1 # via aprsd, sphinx, update-checker -rich==12.6.0 # via aprsd -shellingham==1.5.0 # via aprsd, click-completion -six==1.16.0 # via aprsd, click-completion, eventlet, imapclient -snowballstemmer==2.2.0 # via sphinx -soupsieve==2.3.2.post1 # via aprsd, beautifulsoup4 -sphinx==5.3.0 # via -r requirements-dev.in -sphinxcontrib-applehelp==1.0.2 # via sphinx -sphinxcontrib-devhelp==1.0.2 # via sphinx -sphinxcontrib-htmlhelp==2.0.0 # via sphinx -sphinxcontrib-jsmath==1.0.1 # via sphinx -sphinxcontrib-qthelp==1.0.3 # via sphinx -sphinxcontrib-serializinghtml==1.1.5 # via sphinx -tabulate==0.9.0 # via aprsd -thesmuggler==1.0.1 # via aprsd -toml==0.10.2 # via pre-commit -tomli==2.0.1 # via black, build, coverage, mypy, pep517, pyproject-api, pytest, tox -tox==4.0.14 # via -r requirements-dev.in -typing-extensions==4.4.0 # via black, mypy -ua-parser==0.16.1 # via aprsd, user-agents -update-checker==0.18.0 # via aprsd -urllib3==1.26.13 # via aprsd, requests -user-agents==2.2.0 # via aprsd -virtualenv==20.17.1 # via pre-commit, tox -werkzeug==2.1.2 # via aprsd, flask -wheel==0.38.4 # via pip-tools -wrapt==1.14.1 # via aprsd -zipp==3.11.0 # via aprsd, importlib-metadata - -# The following packages are considered to be unsafe in a requirements file: -# pip -# setuptools diff --git a/requirements.in b/requirements.in index adaf62c..08e7115 100644 --- a/requirements.in +++ b/requirements.in @@ -1,4 +1,5 @@ pbr slack_sdk>=3.0 slackeventsapi>=2.1.0 -aprsd>=2.7.0 +aprsd>=3.0.0 +oslo_config diff --git a/requirements.txt b/requirements.txt index 9efc70e..8617ce8 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,13 +4,13 @@ # # pip-compile --annotation-style=line --resolver=backtracking requirements.in # -aprsd==2.6.1 # via -r requirements.in +aprsd==3.0.0 # via -r requirements.in aprslib==0.7.2 # via aprsd -attrs==22.1.0 # via aprsd, ax253, kiss3 +attrs==22.2.0 # via aprsd, ax253, kiss3 ax253==0.1.5.post1 # via aprsd, kiss3 beautifulsoup4==4.11.1 # via aprsd bidict==0.22.0 # via aprsd, python-socketio -bitarray==2.6.0 # via aprsd, ax253, kiss3 +bitarray==2.6.1 # via aprsd, ax253, kiss3 certifi==2022.12.7 # via aprsd, requests cffi==1.15.1 # via aprsd, cryptography charset-normalizer==2.1.1 # via aprsd, requests @@ -27,7 +27,7 @@ flask-socketio==5.3.2 # via aprsd greenlet==2.0.1 # via aprsd, eventlet idna==3.4 # via aprsd, requests imapclient==2.3.1 # via aprsd -importlib-metadata==5.1.0 # via aprsd, ax253, flask, kiss3 +importlib-metadata==5.2.0 # via aprsd, ax253, flask, kiss3 itsdangerous==2.1.2 # via aprsd, flask jinja2==3.1.2 # via aprsd, click-completion, flask kiss3==8.0.0 # via aprsd @@ -42,7 +42,7 @@ pyserial==3.5 # via aprsd, pyserial-asyncio pyserial-asyncio==0.6 # via aprsd, kiss3 python-engineio==4.3.4 # via aprsd, python-socketio python-socketio==5.7.2 # via aprsd, flask-socketio -pytz==2022.6 # via aprsd +pytz==2022.7 # via aprsd pyyaml==6.0 # via aprsd requests==2.28.1 # via aprsd, update-checker rich==12.6.0 # via aprsd diff --git a/setup.cfg b/setup.cfg index 3b7d511..c2146eb 100644 --- a/setup.cfg +++ b/setup.cfg @@ -17,6 +17,10 @@ description_file = README.rst summary = Amateur radio APRS daemon which listens for messages and responds +[options.entry_points] +oslo.config.opts = + aprsd_slack_plugin.conf = aprsd_slack_plugin.conf.opts:list_opts + [global] setup-hooks = pbr.hooks.setup_hook