Closed johanek closed 1 year ago
Will check the code. If I remember back a bit, something like this caused some issues in the code and was changed to what it is now.
There is also this discussion that outlines the current methodology (if this PR is not an improvement on it): https://github.com/nautobot/pynautobot/discussions/62
what if the app name uses underscores instead of dashes?
@jmcgill298
what if the app name uses underscores instead of dashes?
You're right. This PR works only for specific case, because the plugin Golden Config sets them base_url
with dashes.
Meanwhile in endpoint.py module the URL for request is formed with app's name
, not app's base_url
. I guess that is the point, that's the reason why the original request failed with 404. I mean, it'd work until the name
of plugin app and it's base_url
differed. And this is exactly the case @johanek encountered on.
TL;DR We should get base_url
of a plugin app and use it to form URL rather than use app's package name.
When we're operating with any plugin application (like nb.plugins.golden_config
e. g.), the name of attribute (i. e. the name of the plugin) is being used to form the URL (just like i've mentioned above).
pynautobot/core/app.py:
...
class PluginsApp(object):
...
def __getattr__(self, name):
return App(self.api, "plugins/{}".format(name))
...
...
And that's why if you made something like this, using dashes instead of underscores:
>>> getattr(nb.plugins, 'golden-config').config_compliance.all() # it doesn't raise an error
[]
this wouldn't return 404, this would be a valid request.
I presume this is incorrect logic. We should use actual URL of the plugin application to form URL for API-request. Meanwhile the plugin app should be accessible by it's package name.
To make it clear. The behaviour we should reach, example:
app's base url: golden-config
(see here)
app's package name: nautobot_golden_config
(see here)
The code which supposed to access plugin app:
>>> nb.plugins.nautobot_golden_config.whatever_endpoint_you_want.all()
# and it creates a request to https://<nb_host>/api/plugins/golden-config/config-compliance
I've investigated PluginsApp
and it's installed_plugins()
method to discover how we could get base_url
of the plugin app. However i encountered that Nautobot does not return plugin's base_url
at all. Here what i've got accessing /api/plugins/installed-plugins/
:
GET /api/plugins/installed-plugins/
HTTP 200 OK
API-Version: 1.2
Allow: GET, HEAD, OPTIONS
Content-Type: application/json
Vary: Accept
[
{
"name": "Nautobot Plugin for Nornir",
"package": "nautobot_plugin_nornir",
"author": "Network to Code, LLC",
"author_email": "",
"description": "A plugin/library for using Nornir within Nautobot.",
"verison": "1.0.0",
"version": "1.0.0"
},
{
"name": "Golden Configuration",
"package": "nautobot_golden_config",
"author": "Network to Code, LLC",
"author_email": "[opensource@networktocode.com](mailto:opensource@networktocode.com)",
"description": "A plugin for managing Golden Configurations.",
"verison": "1.3.1",
"version": "1.3.1"
}
]
I don't see plugin's base_url
above.
If we consider Nautobot's _get_plugin_data()
staticmethod of the class InstalledPluginsAPIView
, there is no key base_url
in the result dict as you can see:
@staticmethod
def _get_plugin_data(plugin_app_config):
return {
"name": plugin_app_config.verbose_name,
"package": plugin_app_config.name,
"author": plugin_app_config.author,
"author_email": plugin_app_config.author_email,
"description": plugin_app_config.description,
# 2.0 TODO: Remove verison key/value when bumping to major revision
"verison": plugin_app_config.version,
"version": plugin_app_config.version,
}
However it can be added: "base_url": plugin_app_config.base_url
. Once i added one, the plugin's base_url
appeared in the /api/plugins/installed-plugins/
:
GET /api/plugins/installed-plugins/
HTTP 200 OK
API-Version: 1.2
Allow: GET, HEAD, OPTIONS
Content-Type: application/json
Vary: Accept
[
{
"name": "Nautobot Plugin for Nornir",
"base_url": "plugin-nornir",
"package": "nautobot_plugin_nornir",
"author": "Network to Code, LLC",
"author_email": "",
"description": "A plugin/library for using Nornir within Nautobot.",
"verison": "1.0.0",
"version": "1.0.0"
},
{
"name": "Golden Configuration",
"base_url": "golden-config",
"package": "nautobot_golden_config",
"author": "Network to Code, LLC",
"author_email": "[opensource@networktocode.com](mailto:opensource@networktocode.com)",
"description": "A plugin for managing Golden Configurations.",
"verison": "1.3.1",
"version": "1.3.1"
}
]
And then we are able to know the actual URL of a plugin.
In conclusion. My proposal:
Open an issue in https://github.com/nautobot/nautobot/, suggest to update _get_plugin_data()
with adding base_url
like this:
class InstalledPluginsAPIView(NautobotAPIVersionMixin, APIView):
...
@staticmethod
def _get_plugin_data(plugin_app_config):
return {
"name": plugin_app_config.verbose_name,
"base_url": plugin_app_config.base_url,
"package": plugin_app_config.name,
...
}
...
Update __getattr__()
method of PluginsApp
class, make something like this:
def __getattr__(self, name):
try:
request_for_plugin = Request(
base="{}/plugins/{}".format(
self.api.base_url,
name
),
token=self.api.token,
http_session=self.api.http_session,
)
# make sure the plugin can be accessed by it's package name
request_for_plugin._make_call()
except RequestError as error:
# if not, iterate over installed plugin to try to find `base_url` of the plugin app
if error.req.status_code == 404:
for plugin in self.installed_plugins():
if plugin.get('package') == name and 'base_url' in plugin:
return App(self.api, "plugins/{}".format(plugin['base_url']))
raise error
return App(self.api, "plugins/{}".format(name))
I know, the code above may seem strange at first glance, since it causes to make an additional GET-request to plugin each time we access nb.plugins.some_plugin_of_mine
, but that's the first came to mind, IDK how else we can check the plugin isn't accessible by it's package name and has different URL.
The code above works well:
>>> nb = api("http://localhost:8888", token=os.environ.get("NAUTOBOT_API_TOKEN"))
>>> print(nb.plugins.nautobot_golden_config.config_compliance.all()) # doesn't raise error
[]
@jmcgill298, @jvanderaa what you think?
@pszulczewski is also taking a look at this some.
Great input @johanek and @nautics889!
As mentioned before, the solution proposed by @johanek may affect other app names not only plugins.
Solutions proposed by @nautics889 introduce deviation to the api path documented in API Docs Swagger as you use plugin names and not the exact plugin base_url
on pynautobot
objects. It can also be a long shot to update nautobot with extra methods.
I would be leaning towards replacing _
to -
but I would make it only for plugins, so PluginsApp
__getattr__
:
https://github.com/nautobot/pynautobot/blob/4c3bad3a65dd0d746004dd638e8a33207ee46271/pynautobot/core/app.py#L146
becomes:
return App(self.api, f"plugins/{name.replace('_', '-')}")
Then we only affect plugin names and we fix something what doesn't really work now. And that works with the base-url as documented in the API Docs.
nb_demo.plugins.golden_config.config_compliance.all()
[<pynautobot.core.response.Record ('jcy-bb-01.infra.ntc.com -> Cisco IOS - svc -> True') ...
nb_demo.plugins.firewall.address_object.all()
[<pynautobot.core.response.Record ('nat-destination') ...
nb_demo.plugins.nautobot_device_lifecycle_mgmt.cve.all()
[<pynautobot.core.response.Record ('CVE-2020-3508') at 0x10294f7f0>]
@jvanderaa I am not aware of previous issues you mentioned, what do think about this approach, to replace underscores to dashes only for plugin paths, which are base-urls?
@pszulczewski thank you!
Replacing _
to -
in PluginsApp
seems to me like good solution for now at least.
FYI: i've created topic in Nautobot's discussions related to this problem, so we can return to this further.
@johanek fancy updating this PR with my suggestion above?
@pszulczewski Updated per your suggestion. Let me know if you'd like me to clean the git history
There is a dependency issue with urllib3 as I see container pulls 2.0.2, where we have 1.26.13 specified in poetry.lock. Looking into this issue ...
Currently access app names including a - character results in an error, as underscores are not converted to dashes i.e.: