identity: Add support for domain config

https://docs.openstack.org/api-ref/identity/v3/index.html#create-domain-configuration

Change-Id: I25e5e4266bfa1bf470fa2dcd38e9002950ecebef
This commit is contained in:
Stephen Finucane 2023-08-11 17:48:26 +01:00
parent d7993d779d
commit 60be29a596
9 changed files with 393 additions and 13 deletions

View File

@ -28,6 +28,14 @@ Domain Operations
:members: create_domain, update_domain, delete_domain, get_domain,
find_domain, domains
Domain Config Operations
^^^^^^^^^^^^^^^^^^^^^^^^
.. autoclass:: openstack.identity.v3._proxy.Proxy
:noindex:
:members: create_domain_config, delete_domain_config, get_domain_config,
update_domain_config
Endpoint Operations
^^^^^^^^^^^^^^^^^^^

View File

@ -5,24 +5,15 @@ Identity v2 Resources
---------------------
.. toctree::
:maxdepth: 1
:glob:
v2/extension
v2/role
v2/tenant
v2/user
v2/*
Identity v3 Resources
---------------------
.. toctree::
:maxdepth: 1
:glob:
v3/credential
v3/domain
v3/endpoint
v3/group
v3/policy
v3/project
v3/service
v3/trust
v3/user
v3/*

View File

@ -0,0 +1,12 @@
openstack.identity.v3.domain_config
===================================
.. automodule:: openstack.identity.v3.domain_config
The Domain Class
----------------
The ``DomainConfig`` class inherits from :class:`~openstack.resource.Resource`.
.. autoclass:: openstack.identity.v3.domain_config.DomainConfig
:members:

View File

@ -16,6 +16,7 @@ from openstack.identity.v3 import (
)
from openstack.identity.v3 import credential as _credential
from openstack.identity.v3 import domain as _domain
from openstack.identity.v3 import domain_config as _domain_config
from openstack.identity.v3 import endpoint as _endpoint
from openstack.identity.v3 import federation_protocol as _federation_protocol
from openstack.identity.v3 import group as _group
@ -51,6 +52,7 @@ from openstack.identity.v3 import system as _system
from openstack.identity.v3 import trust as _trust
from openstack.identity.v3 import user as _user
from openstack import proxy
from openstack import resource
from openstack import utils
@ -83,6 +85,8 @@ class Proxy(proxy.Proxy):
"user": _user.User,
}
# ========== Credentials ==========
def create_credential(self, **attrs):
"""Create a new credential from attributes
@ -165,6 +169,8 @@ class Proxy(proxy.Proxy):
"""
return self._update(_credential.Credential, credential, **attrs)
# ========== Domains ==========
def create_domain(self, **attrs):
"""Create a new domain from attributes
@ -244,6 +250,85 @@ class Proxy(proxy.Proxy):
"""
return self._update(_domain.Domain, domain, **attrs)
# ========== Domain configs ==========
def create_domain_config(self, domain, **attrs):
"""Create a new config for a domain from attributes.
:param domain: The value can be the ID of a domain or
a :class:`~openstack.identity.v3.domain.Domain` instance.
:param dict attrs: Keyword arguments which will be used to create a
:class:`~openstack.identity.v3.domain_config.DomainConfig`
comprised of the properties on the DomainConfig class.
:returns: The results of domain config creation
:rtype: :class:`~openstack.identity.v3.domain_config.DomainConfig`
"""
domain_id = resource.Resource._get_id(domain)
return self._create(
_domain_config.DomainConfig,
domain_id=domain_id,
**attrs,
)
def delete_domain_config(self, domain, ignore_missing=True):
"""Delete a config for a domain
:param domain: The value can be the ID of a domain or a
a :class:`~openstack.identity.v3.domain.Domain` instance.
:param bool ignore_missing: When set to ``False``
:class:`~openstack.exceptions.ResourceNotFound` will be
raised when the identity provider does not exist.
When set to ``True``, no exception will be set when
attempting to delete a nonexistent config for a domain.
:returns: ``None``
"""
domain_id = resource.Resource._get_id(domain)
self._delete(
_domain_config.DomainConfig,
domain_id=domain_id,
ignore_missing=ignore_missing,
)
def get_domain_config(self, domain):
"""Get a single config for a domain
:param domain_id: The value can be the ID of a domain or a
:class:`~openstack.identity.v3.domain.Domain` instance.
:returns: One
:class:`~openstack.identity.v3.domain_config.DomainConfig`
:raises: :class:`~openstack.exceptions.ResourceNotFound` when no
resource can be found.
"""
domain_id = resource.Resource._get_id(domain)
return self._get(
_domain_config.DomainConfig,
domain_id=domain_id,
requires_id=False,
)
def update_domain_config(self, domain, **attrs):
"""Update a config for a domain
:param domain_id: The value can be the ID of a domain or a
:class:`~openstack.identity.v3.domain.Domain` instance.
:attrs kwargs: The attributes to update on the config for a domain
represented by ``domain_id``.
:returns: The updated config for a domain
:rtype: :class:`~openstack.identity.v3.domain_config.DomainConfig`
"""
domain_id = resource.Resource._get_id(domain)
return self._update(
_domain_config.DomainConfig,
domain_id=domain_id,
**attrs,
)
# ========== Endpoints ==========
def create_endpoint(self, **attrs):
"""Create a new endpoint from attributes
@ -326,6 +411,8 @@ class Proxy(proxy.Proxy):
"""
return self._update(_endpoint.Endpoint, endpoint, **attrs)
# ========== Groups ==========
def create_group(self, **attrs):
"""Create a new group from attributes
@ -462,6 +549,8 @@ class Proxy(proxy.Proxy):
users = self._list(_user.User, base_path=base_path, **attrs)
return users
# ========== Policies ==========
def create_policy(self, **attrs):
"""Create a new policy from attributes
@ -541,6 +630,8 @@ class Proxy(proxy.Proxy):
"""
return self._update(_policy.Policy, policy, **attrs)
# ========== Project ==========
def create_project(self, **attrs):
"""Create a new project from attributes
@ -638,6 +729,8 @@ class Proxy(proxy.Proxy):
"""
return self._update(_project.Project, project, **attrs)
# ========== Services ==========
def create_service(self, **attrs):
"""Create a new service from attributes
@ -717,6 +810,8 @@ class Proxy(proxy.Proxy):
"""
return self._update(_service.Service, service, **attrs)
# ========== Users ==========
def create_user(self, **attrs):
"""Create a new user from attributes
@ -799,6 +894,8 @@ class Proxy(proxy.Proxy):
"""
return self._update(_user.User, user, **attrs)
# ========== Trusts ==========
def create_trust(self, **attrs):
"""Create a new trust from attributes
@ -865,6 +962,8 @@ class Proxy(proxy.Proxy):
# TODO(briancurtin): This is paginated but requires base list changes.
return self._list(_trust.Trust, **query)
# ========== Regions ==========
def create_region(self, **attrs):
"""Create a new region from attributes
@ -944,6 +1043,8 @@ class Proxy(proxy.Proxy):
"""
return self._update(_region.Region, region, **attrs)
# ========== Roles ==========
def create_role(self, **attrs):
"""Create a new role from attributes
@ -1025,6 +1126,8 @@ class Proxy(proxy.Proxy):
"""
return self._update(_role.Role, role, **attrs)
# ========== Role assignments ==========
def role_assignments_filter(
self, domain=None, project=None, system=None, group=None, user=None
):
@ -1127,6 +1230,8 @@ class Proxy(proxy.Proxy):
"""
return self._list(_role_assignment.RoleAssignment, **query)
# ========== Registered limits ==========
def registered_limits(self, **query):
"""Retrieve a generator of registered_limits
@ -1204,6 +1309,8 @@ class Proxy(proxy.Proxy):
ignore_missing=ignore_missing,
)
# ========== Limits ==========
def limits(self, **query):
"""Retrieve a generator of limits
@ -1267,6 +1374,8 @@ class Proxy(proxy.Proxy):
"""
self._delete(limit.Limit, limit, ignore_missing=ignore_missing)
# ========== Roles ==========
def assign_domain_role_to_user(self, domain, user, role):
"""Assign role to user on a domain
@ -1555,6 +1664,8 @@ class Proxy(proxy.Proxy):
system = self._get_resource(_system.System, system)
return system.validate_group_has_role(self, group, role)
# ========== Application credentials ==========
def application_credentials(self, user, **query):
"""Retrieve a generator of application credentials
@ -1681,6 +1792,8 @@ class Proxy(proxy.Proxy):
ignore_missing=ignore_missing,
)
# ========== Federation protocols ==========
def create_federation_protocol(self, idp_id, **attrs):
"""Create a new federation protocol from attributes
@ -1834,6 +1947,8 @@ class Proxy(proxy.Proxy):
idp_id = idp_id.id
return self._update(cls, protocol, idp_id=idp_id, **attrs)
# ========== Mappings ==========
def create_mapping(self, **attrs):
"""Create a new mapping from attributes
@ -1914,6 +2029,8 @@ class Proxy(proxy.Proxy):
"""
return self._update(_mapping.Mapping, mapping, **attrs)
# ========== Identity providers ==========
def create_identity_provider(self, **attrs):
"""Create a new identity provider from attributes

View File

@ -0,0 +1,47 @@
# 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.
from openstack import resource
class DomainConfigLDAP(resource.Resource):
#: The base distinguished name (DN) of LDAP.
user_tree_dn = resource.Body('user_tree_dn')
#: The LDAP URL.
url = resource.Body('url')
class DomainConfigDriver(resource.Resource):
#: The Identity backend driver.
driver = resource.Body('driver')
class DomainConfig(resource.Resource):
resource_key = 'config'
base_path = '/domains/%(domain_id)s/config'
requires_id = False
create_requires_id = False
commit_method = 'PATCH'
create_method = 'PUT'
# capabilities
allow_create = True
allow_fetch = True
allow_commit = True
allow_delete = True
#: The domain ID.
domain_id = resource.URI('domain_id')
#: An identity object.
identity = resource.Body('identity', type=DomainConfigDriver)
#: The config object.
ldap = resource.Body('ldap', type=DomainConfigLDAP)

View File

@ -0,0 +1,83 @@
# 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.
import uuid
from openstack.identity.v3 import domain as _domain
from openstack.identity.v3 import domain_config as _domain_config
from openstack.tests.functional import base
class TestDomainConfig(base.BaseFunctionalTest):
def setUp(self):
super().setUp()
self.domain_name = self.getUniqueString()
# create the domain and domain config
self.domain = self.operator_cloud.create_domain(
name=self.domain_name,
)
self.assertIsInstance(self.domain, _domain.Domain)
self.addCleanup(self._delete_domain)
def _delete_domain(self):
self.operator_cloud.identity.update_domain(
self.domain,
enabled=False,
)
self.operator_cloud.identity.delete_domain(self.domain)
def test_domain_config(self):
# create the domain config
domain_config = self.operator_cloud.identity.create_domain_config(
self.domain,
identity={'driver': uuid.uuid4().hex},
ldap={'url': uuid.uuid4().hex},
)
self.assertIsInstance(
domain_config,
_domain_config.DomainConfig,
)
# update the domain config
ldap_url = uuid.uuid4().hex
domain_config = self.operator_cloud.identity.update_domain_config(
self.domain,
ldap={'url': ldap_url},
)
self.assertIsInstance(
domain_config,
_domain_config.DomainConfig,
)
# retrieve details of the (updated) domain config
domain_config = self.operator_cloud.identity.get_domain_config(
self.domain,
)
self.assertIsInstance(
domain_config,
_domain_config.DomainConfig,
)
self.assertEqual(ldap_url, domain_config.ldap.url)
# delete the domain config
result = self.operator_cloud.identity.delete_domain_config(
self.domain,
ignore_missing=False,
)
self.assertIsNone(result)

View File

@ -0,0 +1,47 @@
# 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.
from openstack.identity.v3 import domain_config
from openstack.tests.unit import base
EXAMPLE = {
'identity': {
'driver': 'ldap',
},
'ldap': {
'url': 'ldap://myldap.com:389/',
'user_tree_dn': 'ou=Users,dc=my_new_root,dc=org',
},
}
class TestDomainConfig(base.TestCase):
def test_basic(self):
sot = domain_config.DomainConfig()
self.assertEqual('config', sot.resource_key)
self.assertEqual('/domains/%(domain_id)s/config', sot.base_path)
self.assertTrue(sot.allow_create)
self.assertTrue(sot.allow_fetch)
self.assertTrue(sot.allow_commit)
self.assertTrue(sot.allow_delete)
def test_make_it(self):
sot = domain_config.DomainConfig(**EXAMPLE)
self.assertIsInstance(sot.identity, domain_config.DomainConfigDriver)
self.assertEqual(EXAMPLE['identity']['driver'], sot.identity.driver)
self.assertIsInstance(sot.ldap, domain_config.DomainConfigLDAP)
self.assertEqual(EXAMPLE['ldap']['url'], sot.ldap.url)
self.assertEqual(
EXAMPLE['ldap']['user_tree_dn'],
sot.ldap.user_tree_dn,
)

View File

@ -15,6 +15,7 @@ import uuid
from openstack.identity.v3 import _proxy
from openstack.identity.v3 import credential
from openstack.identity.v3 import domain
from openstack.identity.v3 import domain_config
from openstack.identity.v3 import endpoint
from openstack.identity.v3 import group
from openstack.identity.v3 import policy
@ -85,6 +86,75 @@ class TestIdentityProxyDomain(TestIdentityProxyBase):
self.verify_update(self.proxy.update_domain, domain.Domain)
class TestIdentityProxyDomainConfig(TestIdentityProxyBase):
def test_domain_config_create_attrs(self):
self.verify_create(
self.proxy.create_domain_config,
domain_config.DomainConfig,
method_args=['domain_id'],
method_kwargs={},
expected_args=[],
expected_kwargs={
'domain_id': 'domain_id',
},
)
def test_domain_config_delete(self):
self.verify_delete(
self.proxy.delete_domain_config,
domain_config.DomainConfig,
ignore_missing=False,
method_args=['domain_id'],
method_kwargs={},
expected_args=[],
expected_kwargs={
'domain_id': 'domain_id',
},
)
def test_domain_config_delete_ignore(self):
self.verify_delete(
self.proxy.delete_domain_config,
domain_config.DomainConfig,
ignore_missing=True,
method_args=['domain_id'],
method_kwargs={},
expected_args=[],
expected_kwargs={
'domain_id': 'domain_id',
},
)
# no find_domain_config
def test_domain_config_get(self):
self.verify_get(
self.proxy.get_domain_config,
domain_config.DomainConfig,
method_args=['domain_id'],
method_kwargs={},
expected_args=[],
expected_kwargs={
'domain_id': 'domain_id',
'requires_id': False,
},
)
# no domain_configs
def test_domain_config_update(self):
self.verify_update(
self.proxy.update_domain_config,
domain_config.DomainConfig,
method_args=['domain_id'],
method_kwargs={},
expected_args=[],
expected_kwargs={
'domain_id': 'domain_id',
},
)
class TestIdentityProxyEndpoint(TestIdentityProxyBase):
def test_endpoint_create_attrs(self):
self.verify_create(self.proxy.create_endpoint, endpoint.Endpoint)

View File

@ -0,0 +1,5 @@
---
features:
- |
Add support for creating, updating and deleting domain configurations for
the identity service.