Add bus for auto-loading charm modules.
charms_openstack.bus.discover() can now be used to load the openstack charm modules placed under lib/charm/openstack/. This is particularly useful when using the generic provide_charm_instance() code e.g. ``` charms_openstack.bus.discover() with charms_openstack.charm.provide_charm_instance() as ci: ci.some_charm_instance_method() ``` Change-Id: Ic1dfb58a37b3f283bbc5b31b5ea192527fc8e57d
This commit is contained in:
parent
7e026dd1df
commit
a9cd586003
|
@ -0,0 +1,80 @@
|
|||
# Copyright 2014-2018 Canonical Limited.
|
||||
#
|
||||
# This file is part of charms.reactive.
|
||||
#
|
||||
# charms.reactive is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License version 3 as
|
||||
# published by the Free Software Foundation.
|
||||
#
|
||||
# charm-helpers is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import importlib
|
||||
import os
|
||||
import charmhelpers.core.hookenv as hookenv
|
||||
|
||||
# Code below is based on charms.reactive.bus
|
||||
|
||||
|
||||
def discover():
|
||||
"""Discover Openstack handlers based on convention.
|
||||
|
||||
Handlers will be loaded from the following directory and its
|
||||
subdirectories:
|
||||
|
||||
* ``$CHARM_DIR/lib/charm/openstack``
|
||||
|
||||
The Python files will be imported and decorated functions registered.
|
||||
"""
|
||||
search_path = os.path.join(
|
||||
hookenv.charm_dir(), 'lib', 'charm', 'openstack')
|
||||
base_path = os.path.join(hookenv.charm_dir(), 'lib', 'charm')
|
||||
for dirpath, dirnames, filenames in os.walk(search_path):
|
||||
for filename in filenames:
|
||||
filepath = os.path.join(dirpath, filename)
|
||||
_register_handlers_from_file(base_path, filepath)
|
||||
|
||||
|
||||
def _load_module(root, filepath):
|
||||
"""Import the supplied module.
|
||||
|
||||
:param root: Module root directory eg directory that the import is
|
||||
relative to.
|
||||
:type root: str
|
||||
:param filepath: Module file.
|
||||
:type filepath: str
|
||||
"""
|
||||
assert filepath.startswith(root + os.sep)
|
||||
assert filepath.endswith('.py')
|
||||
package = os.path.basename(root)
|
||||
module = filepath[len(root):-3].replace(os.sep, '.')
|
||||
if module.endswith('.__init__'):
|
||||
module = module[:-9]
|
||||
|
||||
# Standard import.
|
||||
importlib.import_module(package + module)
|
||||
|
||||
|
||||
def _register_handlers_from_file(root, filepath):
|
||||
"""Import the supplied module if its a good candidate.
|
||||
|
||||
:param root: Module root directory eg directory that the import is
|
||||
relative to.
|
||||
:type root: str
|
||||
:param filepath: Module file.
|
||||
:type filepath: str
|
||||
"""
|
||||
no_exec_blacklist = (
|
||||
'.md', '.yaml', '.txt', '.ini',
|
||||
'makefile', '.gitignore',
|
||||
'copyright', 'license')
|
||||
if filepath.lower().endswith(no_exec_blacklist):
|
||||
# Don't load handlers with one of the blacklisted extensions
|
||||
return
|
||||
if filepath.endswith('.py'):
|
||||
_load_module(root, filepath)
|
|
@ -0,0 +1,58 @@
|
|||
# Copyright 2014-2018 Canonical Limited.
|
||||
#
|
||||
# This file is part of charms.reactive.
|
||||
#
|
||||
# charms.reactive is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License version 3 as
|
||||
# published by the Free Software Foundation.
|
||||
#
|
||||
# charm-helpers is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import os
|
||||
import unittest
|
||||
|
||||
import mock
|
||||
|
||||
import charms_openstack.bus as bus
|
||||
|
||||
|
||||
class TestBus(unittest.TestCase):
|
||||
|
||||
@mock.patch.object(bus.os, 'walk')
|
||||
@mock.patch.object(bus, '_register_handlers_from_file')
|
||||
@mock.patch('charmhelpers.core.hookenv.charm_dir')
|
||||
def test_discover(self, charm_dir, _register_handlers_from_file, walk):
|
||||
os.walk.return_value = [(
|
||||
'/x/unit-aodh-1/charm/lib/charm/openstack',
|
||||
['__pycache__'],
|
||||
['__init__.py', 'aodh.py'])]
|
||||
|
||||
charm_dir.return_value = '/x/unit-aodh-1/charm'
|
||||
bus.discover()
|
||||
expect_calls = [
|
||||
mock.call(
|
||||
'/x/unit-aodh-1/charm/lib/charm',
|
||||
'/x/unit-aodh-1/charm/lib/charm/openstack/__init__.py'),
|
||||
mock.call(
|
||||
'/x/unit-aodh-1/charm/lib/charm',
|
||||
'/x/unit-aodh-1/charm/lib/charm/openstack/aodh.py')]
|
||||
_register_handlers_from_file.assert_has_calls(expect_calls)
|
||||
|
||||
@mock.patch.object(bus.importlib, 'import_module')
|
||||
def test_load_module(self, import_module):
|
||||
import_module.side_effect = lambda x: x
|
||||
bus._load_module(
|
||||
'/x/charm/lib/charm',
|
||||
'/x/charm/lib/charm/openstack/aodh.py'),
|
||||
import_module.assert_called_once_with('charm.openstack.aodh')
|
||||
|
||||
@mock.patch.object(bus, '_load_module')
|
||||
def test_register_handlers_from_file(self, _load_module):
|
||||
bus._register_handlers_from_file('reactive', 'reactive/foo.py')
|
||||
_load_module.assert_called_once_with('reactive', 'reactive/foo.py')
|
Loading…
Reference in New Issue