-
Notifications
You must be signed in to change notification settings - Fork 5
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
gooseBit plugin/module system design #15
Comments
This can be accomplished by loading plugins before initializing the DB, and appending all plugin modules to the tortoise configuration, here - Lines 6 to 13 in 714ad04
|
I imagine you could have a configuration that looks something like this: plugins:
goosebit_plugins.cfd.provisioner:
db:
models: goosebit_plugins.cfd.provisioner.db.models
... |
I'm thinking the models should be defined by a plugin class property or something, not something that's passed by the config file. |
Yeah, I think maybe just iterate all plugin classes and check each for a |
I never implemented a plug-in mechanism. But besides the technical challenges mentioned above I think it also needs careful modeling of the extension points. This could be tricky to get right upfront. Maybe better to regularly implement the feature and then see if it could be extracted as a plug-in? |
Yeah, figured I would make open an issue to figure out an initial design. At least initially having a stable plugin API probably isn't super important so we can hopefully iterate on the API design a bit before stabilizing it.
Technically we have already implemented the cfd provisioner system as part of the original internal project gooseBit is based off of. At a minimum I think a plugin like the cfd provisioner will need a way to add its own database models/tables that reference base gooseBit tables and a way to add additional pages to the webui. It will also need a way to add request handling hooks to the various DDI API endpoints that can conditionally override default logic such as for overriding the update file being served and receiving config vars. |
I have created a very simple PR for now that should demonstrate what I believe is the best way to do this, following https://setuptools.pypa.io/en/latest/userguide/entry_point.html I for some reason cannot get this to work, and have tried moving around directories to not be stacked for the example plugin, and also renaming the |
Hmm, I'm not sure this is the best approach, at least not by itself, it's unclear how we would say have a plugin that exposes its own configuration options and such here in a way that would tie in nicely. |
After a lot more thought, I think the "plugin class" style of doing this is a bad idea. The problem is that doing it as a class would mean that every time we want to add a new "pluggable" feature, we have to add another item into that class, which will end up being very bloated. I believe the best way to do this is still allowing the plugin to inject functionality directly into routes or other parts of goosebit (which is what the class would have to do anyway), but we remove a layer of indirection. I'm going to aim to start working on an injection system for the update selection (as that's one of the harder ones IMO, and one that we will need internally) some time this week. We can review it then, but broadly I think adding a class just adds unneeded complexity, and if we ever decided we wanted to transition to that, we should start with allowing plugins to inject first then transition to having the injection route through the class. |
There's a number of use cases where users of gooseBit may need to extend the updater application with custom functionality that for various reasons should be maintained separately from the upstream gooseBit project, ideally we want it to be possible to extend gooseBit without having to fork and modify the base project codebase.
For example we want to be able to implement a configuration management extension for provisioning of device keys as a gooseBit plugin/module.
In terms of how plugin configuration would look I'm thinking something like this via the yaml settings file may make sense:
The idea would be that we have a python class/package with the import name
upstream.cfd.provisioner
that extends some sort of base module class from gooseBit which would be passed the decoded plugin child yaml configuration structure(a multilevel dictionary in the above example) when the module__init__
method is called during plugin load after dynamically importing theupstream.cfd.provisioner
python module class.The module class implementation from the plugin project would then be expected to provide implementations for the various hook methods/variables that gooseBit would call in various places such as when the device sends config vars or when device poll various endpoints.
The module system would also likely need a way for plugins to define their own database models/tables which may need to reference the existing gooseBit models so would likely need orm/migration hooks for integrating that as well. Likely we want these plugin defined database models/tables to have some sort of namespacing pattern that ensures they won't conflict with other plugins or the base gooseBit project models/tables.
Some potentially relevant info:
https://github.com/team23/fastapi-module-loader
https://github.com/Renumics/spotlight/blob/main/renumics/spotlight/plugin_loader.py
https://fastapi.tiangolo.com/tutorial/dependencies/#fastapi-plug-ins
https://fastapi.tiangolo.com/features/#unlimited-plug-ins
https://packaging.python.org/en/latest/guides/creating-and-discovering-plugins/
The text was updated successfully, but these errors were encountered: