├── .gitignore ├── .gitreview ├── .stestr.conf ├── .zuul.yaml ├── copyright ├── interface.yaml ├── provides.py ├── requires.py ├── test-requirements.txt └── tox.ini /.gitignore: -------------------------------------------------------------------------------- 1 | .tox 2 | -------------------------------------------------------------------------------- /.gitreview: -------------------------------------------------------------------------------- 1 | [gerrit] 2 | host=review.opendev.org 3 | port=29418 4 | project=openstack/charm-interface-keystone-credentials 5 | -------------------------------------------------------------------------------- /.stestr.conf: -------------------------------------------------------------------------------- 1 | [DEFAULT] 2 | test_path=./unit_tests 3 | top_dir=./ 4 | -------------------------------------------------------------------------------- /.zuul.yaml: -------------------------------------------------------------------------------- 1 | - project: 2 | templates: 3 | - python-charm-interface-jobs 4 | -------------------------------------------------------------------------------- /copyright: -------------------------------------------------------------------------------- 1 | Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0 2 | 3 | Files: * 4 | Copyright: 2015, Canonical Ltd. 5 | License: Apache-2.0 6 | 7 | License: Apache-2.0 8 | Licensed under the Apache License, Version 2.0 (the "License"); 9 | you may not use this file except in compliance with the License. 10 | You may obtain a copy of the License at 11 | . 12 | http://www.apache.org/licenses/LICENSE-2.0 13 | . 14 | Unless required by applicable law or agreed to in writing, software 15 | distributed under the License is distributed on an "AS IS" BASIS, 16 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | See the License for the specific language governing permissions and 18 | limitations under the License. 19 | . 20 | On Debian-based systems the full text of the Apache version 2.0 license 21 | can be found in `/usr/share/common-licenses/Apache-2.0'. 22 | -------------------------------------------------------------------------------- /interface.yaml: -------------------------------------------------------------------------------- 1 | name: keystone-credentials 2 | summary: > 3 | Interface for integrating with Keystone identity credentials 4 | Charms use this relation to obtain keystone credentials 5 | without creating a service catalog entry. Set 'username' 6 | only on the relation and keystone will set defaults and 7 | return authentication details. Possible relation settings: 8 | username: Username to be created. 9 | project: Project (tenant) name to be created. Defaults to services 10 | project. 11 | requested_roles: Comma delimited list of roles to be created 12 | requested_grants: Comma delimited list of roles to be granted. 13 | Defaults to Admin role. 14 | domain: Keystone v3 domain the user will be created in. Defaults 15 | to the Default domain. 16 | maintainer: OpenStack Charmers 17 | -------------------------------------------------------------------------------- /provides.py: -------------------------------------------------------------------------------- 1 | # Licensed under the Apache License, Version 2.0 (the "License"); 2 | # you may not use this file except in compliance with the License. 3 | # You may obtain a copy of the License at 4 | # 5 | # http://www.apache.org/licenses/LICENSE-2.0 6 | # 7 | # Unless required by applicable law or agreed to in writing, software 8 | # distributed under the License is distributed on an "AS IS" BASIS, 9 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | # See the License for the specific language governing permissions and 11 | # limitations under the License. 12 | 13 | from charms.reactive import RelationBase 14 | from charms.reactive import hook 15 | from charms.reactive import scopes 16 | 17 | 18 | class KeystoneProvides(RelationBase): 19 | scope = scopes.GLOBAL 20 | 21 | @hook('{provides:keystone-credentials}-relation-joined') 22 | def joined(self): 23 | self.set_flag('{relation_name}.connected') 24 | 25 | @hook('{provides:keystone-credentials}-relation-{broken,departed}') 26 | def departed(self): 27 | self.clear_flag('{relation_name}.connected') 28 | 29 | def expose_credentials(self, credentials): 30 | """Expose Keystone credentials to related units. 31 | 32 | :param credentials: The Keystone credentials to be exposed. 33 | :type credentials: dict 34 | """ 35 | self.set_remote(**credentials) 36 | -------------------------------------------------------------------------------- /requires.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # Licensed under the Apache License, Version 2.0 (the "License"); 3 | # you may not use this file except in compliance with the License. 4 | # You may obtain a copy of the License at 5 | # 6 | # http://www.apache.org/licenses/LICENSE-2.0 7 | # 8 | # Unless required by applicable law or agreed to in writing, software 9 | # distributed under the License is distributed on an "AS IS" BASIS, 10 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | # See the License for the specific language governing permissions and 12 | # limitations under the License. 13 | 14 | from charmhelpers.core import hookenv 15 | from charms.reactive import RelationBase 16 | from charms.reactive import hook 17 | from charms.reactive import scopes 18 | 19 | 20 | class KeystoneRequires(RelationBase): 21 | scope = scopes.GLOBAL 22 | 23 | # These remote data fields will be automatically mapped to accessors 24 | # with a basic documentation string provided. 25 | 26 | auto_accessors = ['private-address', 'credentials_host', 27 | 'credentials_protocol', 'credentials_port', 28 | 'credentials_project', 'credentials_username', 29 | 'credentials_password', 'credentials_project_id', 30 | 'credentials_project_domain_id', 31 | 'credentials_user_domain_id', 32 | 'credentials_project_domain_name', 33 | 'credentials_user_domain_name', 34 | 'api_version', 'auth_host', 'auth_protocol', 'auth_port', 35 | 'region', 'ca_cert', 'https_keystone'] 36 | 37 | @hook('{requires:keystone-credentials}-relation-joined') 38 | def joined(self): 39 | self.set_state('{relation_name}.connected') 40 | self.update_state() 41 | 42 | def update_state(self): 43 | """Update the states of the relations based on the data that the 44 | relation has. 45 | 46 | If the :meth:`base_data_complete` is False then all of the states 47 | are removed. Otherwise, the individual states are set according to 48 | their own data methods. 49 | """ 50 | base_complete = self.base_data_complete() 51 | states = { 52 | '{relation_name}.available': True, 53 | '{relation_name}.available.ssl': self.ssl_data_complete(), 54 | '{relation_name}.available.auth': self.auth_data_complete() 55 | } 56 | for k, v in states.items(): 57 | if base_complete and v: 58 | self.set_state(k) 59 | else: 60 | self.remove_state(k) 61 | 62 | @hook('{requires:keystone-credentials}-relation-changed') 63 | def changed(self): 64 | self.update_state() 65 | self.set_state('{relation_name}.available.updated') 66 | hookenv.atexit(self._clear_updated) 67 | 68 | @hook('{requires:keystone-credentials}-relation-{broken,departed}') 69 | def departed(self): 70 | self.update_state() 71 | 72 | def base_data_complete(self): 73 | data = { 74 | 'private-address': self.private_address(), 75 | 'credentials_host': self.credentials_host(), 76 | 'credentials_protocol': self.credentials_protocol(), 77 | 'credentials_port': self.credentials_port(), 78 | 'api_version': self.api_version(), 79 | 'auth_host': self.auth_host(), 80 | 'auth_protocol': self.auth_protocol(), 81 | 'auth_port': self.auth_port(), 82 | } 83 | if all(data.values()): 84 | return True 85 | return False 86 | 87 | def auth_data_complete(self): 88 | data = { 89 | 'credentials_project': self.credentials_project(), 90 | 'credentials_username': self.credentials_username(), 91 | 'credentials_password': self.credentials_password(), 92 | 'credentials_project_id': self.credentials_project_id(), 93 | } 94 | if all(data.values()): 95 | return True 96 | return False 97 | 98 | def ssl_data_complete(self): 99 | data = { 100 | 'https_keystone': self.https_keystone(), 101 | 'ca_cert': self.ca_cert(), 102 | } 103 | for value in data.values(): 104 | if not value or value == '__null__': 105 | return False 106 | return True 107 | 108 | def request_credentials(self, username, project=None, region=None, 109 | requested_roles=None, requested_grants=None, 110 | domain=None): 111 | """ 112 | Request credentials from Keystone 113 | 114 | :side effect: set requested paramaters on the identity-credentials 115 | relation 116 | 117 | Required parameter 118 | :param username: Username to be created. 119 | 120 | Optional parametrs 121 | :param project: Project (tenant) name to be created. Defaults to 122 | services project. 123 | :param requested_roles: Comma delimited list of roles to be created 124 | :param requested_grants: Comma delimited list of roles to be granted. 125 | Defaults to Admin role. 126 | :param domain: Keystone v3 domain the user will be created in. Defaults 127 | to the Default domain. 128 | """ 129 | relation_info = { 130 | 'username': username, 131 | 'project': project, 132 | 'requested_roles': requested_roles, 133 | 'requested_grants': requested_grants, 134 | 'domain': domain, 135 | } 136 | 137 | self.set_local(**relation_info) 138 | self.set_remote(**relation_info) 139 | 140 | def _clear_updated(self): 141 | self.remove_state('{relation_name}.available.updated') 142 | -------------------------------------------------------------------------------- /test-requirements.txt: -------------------------------------------------------------------------------- 1 | flake8>=2.2.4 2 | stestr>=2.2.0 3 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = pep8,py35 3 | skipsdist = True 4 | # NOTE(beisner): Avoid build/test env pollution by not enabling sitepackages. 5 | sitepackages = False 6 | # NOTE(beisner): Avoid false positives by not skipping missing interpreters. 7 | skip_missing_interpreters = False 8 | 9 | [testenv] 10 | setenv = VIRTUAL_ENV={envdir} 11 | PYTHONHASHSEED=0 12 | install_command = 13 | pip install {opts} {packages} 14 | commands = stestr run {posargs} 15 | 16 | [testenv:py35] 17 | basepython = python3.5 18 | deps = -r{toxinidir}/test-requirements.txt 19 | # TODO: Need to write unit tests then remove the following command. 20 | commands = /bin/true 21 | 22 | [testenv:py3] 23 | basepython = python3 24 | deps = -r{toxinidir}/test-requirements.txt 25 | # TODO: Need to write unit tests then remove the following command. 26 | commands = /bin/true 27 | 28 | [testenv:pep8] 29 | basepython = python3 30 | deps = -r{toxinidir}/test-requirements.txt 31 | commands = flake8 {posargs} 32 | 33 | [testenv:venv] 34 | basepython = python3 35 | commands = {posargs} 36 | 37 | [flake8] 38 | ignore = E402,E226 39 | --------------------------------------------------------------------------------