From 5e3ca820b3093227e848220db823ce4b9c9eb8b2 Mon Sep 17 00:00:00 2001 From: Paul Barker Date: Fri, 16 Oct 2020 19:31:47 +0200 Subject: [PATCH] plugins: Refactor plugin identification and access This change moves all plugin handling code into the kas.plugins module. New accessor functions `plugins.get(name)` and `plugins.all()` are provided to wrap the plugins dictionary so that the kas main function doesn't need to worry about how this is accessed. Plugins are loaded at runtime rather than at parse time by calling `plugins.load()` which gives us an improved ability to handle errors. The `@kasplugin` decorator is removed as it modified and attribute on the kasplugin function itself when a plugin module was loaded. Importing a module should not result in changes to a variable in a different module as it leads to an initialization code flow which is difficult to reason about. Instead, plugin modules should now list the plugins which they introduce in a `__KAS_PLUGINS__` list which will be walked at runtime by `plugins.load()`. Signed-off-by: Paul Barker Signed-off-by: Jan Kiszka --- kas/kas.py | 20 +++++++------------- kas/plugins/__init__.py | 38 +++++++++++++++++++++++++++++++------- kas/plugins/build.py | 5 +++-- kas/plugins/shell.py | 5 +++-- 4 files changed, 44 insertions(+), 24 deletions(-) diff --git a/kas/kas.py b/kas/kas.py index b276b85..89358e5 100644 --- a/kas/kas.py +++ b/kas/kas.py @@ -42,15 +42,7 @@ except ImportError: HAVE_COLORLOG = False from . import __version__, __file_version__, __compatible_file_version__ - -# Import kas plugins -# Since they are added by decorators, they don't need to be called, -# just imported. -from .plugins import kasplugin -from .plugins import build -from .plugins import shell - -__all__ = ['build', 'shell'] +from . import plugins __license__ = 'MIT' __copyright__ = 'Copyright (c) Siemens AG, 2017-2018' @@ -131,7 +123,7 @@ def kas_get_argparser(): subparser = parser.add_subparsers(help='sub command help', dest='cmd') - for plugin in getattr(kasplugin, 'plugins', {}).values(): + for plugin in plugins.all(): plugin_parser = subparser.add_parser(plugin.name, help=plugin.helpmsg) setup_parser_common_args(plugin_parser) plugin.setup_parser(plugin_parser) @@ -144,6 +136,7 @@ def kas(argv): The actual main entry point of kas. """ create_logger() + plugins.load() parser = kas_get_argparser() args = parser.parse_args(argv) @@ -159,9 +152,10 @@ def kas(argv): loop.add_signal_handler(sig, interruption) atexit.register(_atexit_handler) - if args.cmd: - plugin = getattr(kasplugin, 'plugins', {})[args.cmd] - plugin().run(args) + plugin_class = plugins.get(args.cmd) + if plugin_class: + plugin = plugin_class() + plugin.run(args) else: parser.print_help() diff --git a/kas/plugins/__init__.py b/kas/plugins/__init__.py index 6c3e1b2..425ed55 100644 --- a/kas/plugins/__init__.py +++ b/kas/plugins/__init__.py @@ -23,13 +23,37 @@ This module contains and manages kas plugins """ +PLUGINS = {} -def kasplugin(plugin_class): + +def register_plugins(mod): """ - A decorator that registers kas plugins + Register all kas plugins found in a module """ - if not hasattr(kasplugin, 'plugins'): - setattr(kasplugin, 'plugins', {}) - getattr(kasplugin, 'plugins').update({ - plugin_class.name: plugin_class - }) + for plugin in getattr(mod, '__KAS_PLUGINS__', []): + PLUGINS[plugin.name] = plugin + + +def load(): + """ + Import all kas plugins + """ + from . import build + from . import shell + + register_plugins(build) + register_plugins(shell) + + +def get(name): + """ + Lookup a kas plugin class by name + """ + return PLUGINS.get(name, None) + + +def all(): + """ + Get a list of all loaded kas plugin classes + """ + return PLUGINS.values() diff --git a/kas/plugins/build.py b/kas/plugins/build.py index 45b6dc3..f1861c0 100644 --- a/kas/plugins/build.py +++ b/kas/plugins/build.py @@ -35,13 +35,11 @@ from kas.libcmds import (Macro, Command, SetupDir, CleanupSSHAgent, WriteBBConfig, SetupHome, ReposApplyPatches, Loop, InitSetupRepos, FinishSetupRepos, SetupReposStep) -from kas.plugins import kasplugin __license__ = 'MIT' __copyright__ = 'Copyright (c) Siemens AG, 2017-2018' -@kasplugin class Build: """ This class implements the build plugin for kas. @@ -135,3 +133,6 @@ class BuildCommand(Command): sys.exit(ret) else: run_cmd(cmd, cwd=ctx.build_dir) + + +__KAS_PLUGINS__ = [Build] diff --git a/kas/plugins/shell.py b/kas/plugins/shell.py index b60a01e..a2958b1 100644 --- a/kas/plugins/shell.py +++ b/kas/plugins/shell.py @@ -35,13 +35,11 @@ from kas.libcmds import (Macro, Command, SetupDir, SetupEnviron, CleanupSSHAgent, SetupSSHAgent, Loop, InitSetupRepos, FinishSetupRepos, SetupReposStep) -from kas.plugins import kasplugin __license__ = 'MIT' __copyright__ = 'Copyright (c) Siemens AG, 2017-2018' -@kasplugin class Shell: """ Implements a kas plugin that opens a shell within the kas environment. @@ -129,3 +127,6 @@ class ShellCommand(Command): if ret != 0: logging.error('Shell returned non-zero exit status %d', ret) sys.exit(ret) + + +__KAS_PLUGINS__ = [Shell]