Add barbican-secrets interface code and unit tests
This commit is contained in:
parent
ea50b1e502
commit
82acd4465e
2
.gitignore
vendored
2
.gitignore
vendored
@ -3,3 +3,5 @@
|
||||
*__pycache__*
|
||||
*.pyc
|
||||
build
|
||||
.unit-state.db
|
||||
*.swp
|
||||
|
6
.travis.yml
Normal file
6
.travis.yml
Normal file
@ -0,0 +1,6 @@
|
||||
language: python
|
||||
python:
|
||||
- "3.6"
|
||||
install: pip install tox-travis
|
||||
script:
|
||||
- tox
|
@ -2,3 +2,10 @@ name: barbican-secrets
|
||||
summary: Interface for a secrets plugin to the Barbican charm.
|
||||
maintainer: OpenStack Charmers <openstack-charmers@lists.ubuntu.com>
|
||||
repo: https://github.com/openstack-charmers/charm-interface-barbican-secrets
|
||||
ignore:
|
||||
- 'unit_tests'
|
||||
- '.stestr.conf'
|
||||
- 'test-requirements.txt'
|
||||
- 'tox.ini'
|
||||
- '.gitignore'
|
||||
- '.travis.yml'
|
||||
|
28
provides.py
Normal file
28
provides.py
Normal file
@ -0,0 +1,28 @@
|
||||
# Copyright 2018 Canonical Ltd
|
||||
#
|
||||
# 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.
|
||||
|
||||
# the reactive framework unfortunately does not grok `import as` in conjunction
|
||||
# with decorators on class instance methods, so we have to revert to `from ...`
|
||||
# imports
|
||||
from charms.reactive import Endpoint
|
||||
|
||||
|
||||
class BarbicanSecretsProvides(Endpoint):
|
||||
"""This is the barbican-{type}secrets end of the relation."""
|
||||
|
||||
def publish_plugin_info(self, name, data, reference=None):
|
||||
for relation in self.relations:
|
||||
relation.to_publish['name'] = name
|
||||
relation.to_publish['data'] = data
|
||||
relation.to_publish['reference'] = reference
|
73
requires.py
Normal file
73
requires.py
Normal file
@ -0,0 +1,73 @@
|
||||
# Copyright 2018 Canonical Ltd
|
||||
#
|
||||
# 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.
|
||||
|
||||
# the reactive framework unfortunately does not grok `import as` in conjunction
|
||||
# with decorators on class instance methods, so we have to revert to `from ...`
|
||||
# imports
|
||||
from charms.reactive import Endpoint, clear_flag, set_flag, when_any, when_not
|
||||
|
||||
|
||||
class BarbicanSecretsRequires(Endpoint):
|
||||
"""This is the Barbican 'end' of the relation."""
|
||||
|
||||
@when_any('endpoint.{endpoint_name}.changed.name',
|
||||
'endpoint.{endpoint_name}.changed.reference',
|
||||
'endpoint.{endpoint_name}.changed.data')
|
||||
def new_plugin(self):
|
||||
set_flag(
|
||||
self.expand_name('{endpoint_name}.new-plugin'))
|
||||
clear_flag(
|
||||
self.expand_name('{endpoint_name}.changed.name'))
|
||||
clear_flag(
|
||||
self.expand_name('{endpoint_name}.changed.reference'))
|
||||
clear_flag(
|
||||
self.expand_name('{endpoint_name}.changed.data'))
|
||||
|
||||
@when_not('endpoint.{endpoint_name}.joined')
|
||||
def broken(self):
|
||||
clear_flag(
|
||||
self.expand_name('{endpoint_name}.new-plugin'))
|
||||
|
||||
@property
|
||||
def plugins(self):
|
||||
for relation in self.relations:
|
||||
for unit in relation.units:
|
||||
name = unit.received['name']
|
||||
reference = unit.received['reference']
|
||||
data = unit.received['data']
|
||||
if not (name and data):
|
||||
continue
|
||||
yield {
|
||||
'name': name,
|
||||
'reference': reference,
|
||||
'data': data,
|
||||
'relation_id': relation.relation_id,
|
||||
'remote_unit_name': unit.unit_name,
|
||||
}
|
||||
|
||||
@property
|
||||
def plugins_string(self):
|
||||
def plugin_name_or_reference():
|
||||
for relation in self.relations:
|
||||
for unit in relation.units:
|
||||
name = unit.received['name']
|
||||
reference = unit.received['reference']
|
||||
data = unit.received['data']
|
||||
if not (name and data):
|
||||
continue
|
||||
if reference:
|
||||
yield reference
|
||||
else:
|
||||
yield name + '_plugin'
|
||||
return ','.join(plugin_name_or_reference())
|
@ -1,3 +1,7 @@
|
||||
flake8>=2.2.4,<=2.4.1
|
||||
# Lint and unit test requirements
|
||||
flake8
|
||||
os-testr>=0.4.1
|
||||
charm-tools
|
||||
charms.reactive
|
||||
mock>=1.2
|
||||
coverage>=3.6
|
||||
git+https://github.com/openstack/charms.openstack.git#egg=charms.openstack
|
||||
|
@ -11,3 +11,12 @@
|
||||
# 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.
|
||||
|
||||
import sys
|
||||
|
||||
sys.path.append('src')
|
||||
sys.path.append('src/lib')
|
||||
|
||||
# Mock out charmhelpers so that we can test without it.
|
||||
import charms_openstack.test_mocks # noqa
|
||||
charms_openstack.test_mocks.mock_charmhelpers()
|
||||
|
@ -11,3 +11,108 @@
|
||||
# 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.
|
||||
import mock
|
||||
import unittest
|
||||
|
||||
import provides
|
||||
|
||||
|
||||
_hook_args = {}
|
||||
|
||||
|
||||
def mock_hook(*args, **kwargs):
|
||||
|
||||
def inner(f):
|
||||
# remember what we were passed. Note that we can't actually determine
|
||||
# the class we're attached to, as the decorator only gets the function.
|
||||
_hook_args[f.__name__] = dict(args=args, kwargs=kwargs)
|
||||
return f
|
||||
return inner
|
||||
|
||||
|
||||
class TestBarbicanSecretsProvides(unittest.TestCase):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
cls._patched_hook = mock.patch('charms.reactive.hook', mock_hook)
|
||||
cls._patched_hook_started = cls._patched_hook.start()
|
||||
# force providesto rerun the mock_hook decorator:
|
||||
# try except is Python2/Python3 compatibility as Python3 has moved
|
||||
# reload to importlib.
|
||||
try:
|
||||
reload(provides)
|
||||
except NameError:
|
||||
import importlib
|
||||
importlib.reload(provides)
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
cls._patched_hook.stop()
|
||||
cls._patched_hook_started = None
|
||||
cls._patched_hook = None
|
||||
# and fix any breakage we did to the module
|
||||
try:
|
||||
reload(provides)
|
||||
except NameError:
|
||||
import importlib
|
||||
importlib.reload(provides)
|
||||
|
||||
def setUp(self):
|
||||
self.bsr = provides.BarbicanSecretsProvides('some-relation', [])
|
||||
self._patches = {}
|
||||
self._patches_start = {}
|
||||
|
||||
def tearDown(self):
|
||||
self.bsr = None
|
||||
for k, v in self._patches.items():
|
||||
v.stop()
|
||||
setattr(self, k, None)
|
||||
self._patches = None
|
||||
self._patches_start = None
|
||||
|
||||
def patch_bsr(self, attr, return_value=None):
|
||||
mocked = mock.patch.object(self.bsr, attr)
|
||||
self._patches[attr] = mocked
|
||||
started = mocked.start()
|
||||
started.return_value = return_value
|
||||
self._patches_start[attr] = started
|
||||
setattr(self, attr, started)
|
||||
|
||||
def patch_topublish(self):
|
||||
self.patch_bsr('_relations')
|
||||
relation = mock.MagicMock()
|
||||
to_publish = mock.PropertyMock()
|
||||
type(relation).to_publish = to_publish
|
||||
self._relations.__iter__.return_value = [relation]
|
||||
return relation.to_publish
|
||||
|
||||
def test_registered_hooks(self):
|
||||
# test that the hooks actually registered the relation expressions that
|
||||
# are meaningful for this interface: this is to handle regressions.
|
||||
# The keys are the function names that the hook attaches to.
|
||||
hook_patterns = {
|
||||
'joined': ('{provides:barbican-secrets}-relation-joined', ),
|
||||
'changed': ('{provides:barbican-secrets}-relation-changed', ),
|
||||
'departed': (
|
||||
'{provides:barbican-secrets}-relation-{broken,departed}', ),
|
||||
}
|
||||
for k, v in _hook_args.items():
|
||||
self.assertEqual(hook_patterns[k], v['args'])
|
||||
|
||||
def test_publish_plugin_info(self):
|
||||
to_publish = self.patch_topublish()
|
||||
name = 'FAKENAME'
|
||||
reference = 'FAKEREFERENCE'
|
||||
data = {'a': 'A', 'b': 'B'}
|
||||
self.bsr.publish_plugin_info(name, data, reference=reference)
|
||||
to_publish.__setitem__.assert_has_calls([
|
||||
mock.call('name', name),
|
||||
mock.call('data', data),
|
||||
mock.call('reference', reference),
|
||||
])
|
||||
self.bsr.publish_plugin_info(name, data)
|
||||
to_publish.__setitem__.assert_has_calls([
|
||||
mock.call('name', name),
|
||||
mock.call('data', data),
|
||||
mock.call('reference', None),
|
||||
])
|
||||
|
@ -11,3 +11,97 @@
|
||||
# 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.
|
||||
import mock
|
||||
|
||||
import requires
|
||||
|
||||
import charms_openstack.test_utils as test_utils
|
||||
|
||||
|
||||
_hook_args = {}
|
||||
|
||||
|
||||
class TestRegisteredHooks(test_utils.TestRegisteredHooks):
|
||||
|
||||
def test_hooks(self):
|
||||
defaults = []
|
||||
hook_set = {
|
||||
'when_any': {
|
||||
'new_plugin': ('endpoint.{endpoint_name}.changed.name',
|
||||
'endpoint.{endpoint_name}.changed.reference',
|
||||
'endpoint.{endpoint_name}.changed.data',),
|
||||
},
|
||||
'when_not': {
|
||||
'broken': ('endpoint.{endpoint_name}.joined',),
|
||||
},
|
||||
}
|
||||
# test that the hooks were registered via the
|
||||
# reactive.barbican_handlers
|
||||
self.registered_hooks_test_helper(requires, hook_set, defaults)
|
||||
|
||||
|
||||
class TestBarbicanSecretRequires(test_utils.PatchHelper):
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.bsr = requires.BarbicanSecretsRequires('some-relation', [])
|
||||
self._patches = {}
|
||||
self._patches_start = {}
|
||||
|
||||
def tearDown(self):
|
||||
self.bsr = None
|
||||
for k, v in self._patches.items():
|
||||
v.stop()
|
||||
setattr(self, k, None)
|
||||
self._patches = None
|
||||
self._patches_start = None
|
||||
|
||||
def patch_bsr(self, attr, return_value=None):
|
||||
mocked = mock.patch.object(self.bsr, attr)
|
||||
self._patches[attr] = mocked
|
||||
started = mocked.start()
|
||||
started.return_value = return_value
|
||||
self._patches_start[attr] = started
|
||||
setattr(self, attr, started)
|
||||
|
||||
def test_new_plugin(self):
|
||||
self.patch_object(requires, 'set_flag')
|
||||
self.patch_object(requires, 'clear_flag')
|
||||
self.bsr.new_plugin()
|
||||
self.set_flag.assert_called_with('some-relation.new-plugin')
|
||||
self.clear_flag.assert_has_calls([
|
||||
mock.call('some-relation.changed.name'),
|
||||
mock.call('some-relation.changed.reference'),
|
||||
mock.call('some-relation.changed.data'),
|
||||
])
|
||||
|
||||
def test_broken(self):
|
||||
self.patch_object(requires, 'clear_flag')
|
||||
self.bsr.broken()
|
||||
self.clear_flag.assert_called_with('some-relation.new-plugin')
|
||||
|
||||
def test_plugins(self):
|
||||
self.patch_bsr('_relations')
|
||||
relation = mock.MagicMock()
|
||||
unit = mock.PropertyMock()
|
||||
type(relation).units = [unit]
|
||||
relation.relation_id = 'RELATION_ID'
|
||||
self._relations.__iter__.return_value = [relation]
|
||||
result = next(self.bsr.plugins)
|
||||
self.assertEqual(result['name'], unit.received['name'])
|
||||
self.assertEqual(result['reference'], unit.received['reference'])
|
||||
self.assertEqual(result['data'], unit.received['data'])
|
||||
self.assertEqual(result['relation_id'], relation.relation_id)
|
||||
self.assertEqual(result['remote_unit_name'], unit.unit_name)
|
||||
|
||||
def test_plugins_string(self):
|
||||
self.patch_bsr('_relations')
|
||||
relation = mock.MagicMock()
|
||||
unit = mock.MagicMock()
|
||||
unit.received = {'name': 'NAME', 'reference': None, 'data': 'DATA'}
|
||||
relation.units = [unit]
|
||||
self._relations.__iter__.return_value = [relation]
|
||||
self.assertEqual(self.bsr.plugins_string, 'NAME_plugin')
|
||||
unit.received = {'name': 'NAME', 'reference': 'PLUGINREFERENCE',
|
||||
'data': 'DATA'}
|
||||
self.assertEqual(self.bsr.plugins_string, 'PLUGINREFERENCE')
|
||||
|
Loading…
x
Reference in New Issue
Block a user