diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-18 05:52:22 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-18 05:52:22 +0000 |
commit | 38b7c80217c4e72b1d8988eb1e60bb6e77334114 (patch) | |
tree | 356e9fd3762877d07cde52d21e77070aeff7e789 /ansible_collections/openstack/cloud/plugins/module_utils | |
parent | Adding upstream version 7.7.0+dfsg. (diff) | |
download | ansible-38b7c80217c4e72b1d8988eb1e60bb6e77334114.tar.xz ansible-38b7c80217c4e72b1d8988eb1e60bb6e77334114.zip |
Adding upstream version 9.4.0+dfsg.upstream/9.4.0+dfsg
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'ansible_collections/openstack/cloud/plugins/module_utils')
3 files changed, 261 insertions, 61 deletions
diff --git a/ansible_collections/openstack/cloud/plugins/module_utils/ironic.py b/ansible_collections/openstack/cloud/plugins/module_utils/ironic.py index a7ab19ef2..44f53560b 100644 --- a/ansible_collections/openstack/cloud/plugins/module_utils/ironic.py +++ b/ansible_collections/openstack/cloud/plugins/module_utils/ironic.py @@ -1,3 +1,6 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + # This code is part of Ansible, but is an independent component. # This particular file snippet, and this file snippet only, is BSD licensed. # Modules you write using this snippet, which is embedded dynamically by Ansible @@ -30,8 +33,8 @@ from ansible_collections.openstack.cloud.plugins.module_utils.openstack import o def ironic_argument_spec(**kwargs): spec = dict( - auth_type=dict(required=False), - ironic_url=dict(required=False), + auth_type=dict(), + ironic_url=dict(), ) spec.update(kwargs) return openstack_full_argument_spec(**spec) diff --git a/ansible_collections/openstack/cloud/plugins/module_utils/openstack.py b/ansible_collections/openstack/cloud/plugins/module_utils/openstack.py index 8663d2fca..2f365121e 100644 --- a/ansible_collections/openstack/cloud/plugins/module_utils/openstack.py +++ b/ansible_collections/openstack/cloud/plugins/module_utils/openstack.py @@ -1,3 +1,6 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + # This code is part of Ansible, but is an independent component. # This particular file snippet, and this file snippet only, is BSD licensed. # Modules you write using this snippet, which is embedded dynamically by Ansible @@ -43,41 +46,13 @@ import importlib import os from ansible.module_utils.basic import AnsibleModule -from ansible.module_utils.six import iteritems - -OVERRIDES = {'os_client_config': 'config', - 'os_endpoint': 'catalog_endpoint', - 'os_flavor': 'compute_flavor', - 'os_flavor_info': 'compute_flavor_info', - 'os_group': 'identity_group', - 'os_group_info': 'identity_group_info', - 'os_ironic': 'baremetal_node', - 'os_ironic_inspect': 'baremetal_inspect', - 'os_ironic_node': 'baremetal_node_action', - 'os_keystone_domain': 'identity_domain', - 'os_keystone_domain_info': 'identity_domain_info', - 'os_keystone_endpoint': 'endpoint', - 'os_keystone_identity_provider': 'federation_idp', - 'os_keystone_identity_provider_info': 'federation_idp_info', - 'os_keystone_mapping': 'federation_mapping', - 'os_keystone_mapping_info': 'federation_mapping_info', - 'os_keystone_role': 'identity_role', - 'os_keystone_service': 'catalog_service', - 'os_listener': 'lb_listener', - 'os_member': 'lb_member', - 'os_nova_flavor': 'compute_flavor', - 'os_nova_host_aggregate': 'host_aggregate', - 'os_pool': 'lb_pool', - 'os_user': 'identity_user', - 'os_user_group': 'group_assignment', - 'os_user_info': 'identity_user_info', - 'os_user_role': 'role_assignment', - 'os_zone': 'dns_zone'} + +OVERRIDES = {} CUSTOM_VAR_PARAMS = ['min_ver', 'max_ver'] -MINIMUM_SDK_VERSION = '0.36.0' -MAXIMUM_SDK_VERSION = '0.98.999' +MINIMUM_SDK_VERSION = '1.0.0' +MAXIMUM_SDK_VERSION = None def ensure_compatibility(version, min_version=None, max_version=None): @@ -130,7 +105,6 @@ def openstack_argument_spec(): login_username=dict(default=OS_USERNAME), auth_url=dict(default=OS_AUTH_URL), region_name=dict(default=OS_REGION_NAME), - availability_zone=dict(), ) if OS_PASSWORD: spec['login_password'] = dict(default=OS_PASSWORD) @@ -143,39 +117,25 @@ def openstack_argument_spec(): return spec -def openstack_find_nova_addresses(addresses, ext_tag, key_name=None): - - ret = [] - for (k, v) in iteritems(addresses): - if key_name and k == key_name: - ret.extend([addrs['addr'] for addrs in v]) - else: - for interface_spec in v: - if 'OS-EXT-IPS:type' in interface_spec and interface_spec['OS-EXT-IPS:type'] == ext_tag: - ret.append(interface_spec['addr']) - return ret - - def openstack_full_argument_spec(**kwargs): spec = dict( - cloud=dict(default=None, type='raw'), - auth_type=dict(default=None), - auth=dict(default=None, type='dict', no_log=True), - region_name=dict(default=None), - availability_zone=dict(default=None), - validate_certs=dict(default=None, type='bool', aliases=['verify']), - ca_cert=dict(default=None, aliases=['cacert']), - client_cert=dict(default=None, aliases=['cert']), - client_key=dict(default=None, no_log=True, aliases=['key']), + cloud=dict(type='raw'), + auth_type=dict(), + auth=dict(type='dict', no_log=True), + region_name=dict(), + validate_certs=dict(type='bool', aliases=['verify']), + ca_cert=dict(aliases=['cacert']), + client_cert=dict(aliases=['cert']), + client_key=dict(no_log=True, aliases=['key']), wait=dict(default=True, type='bool'), timeout=dict(default=180, type='int'), - api_timeout=dict(default=None, type='int'), + api_timeout=dict(type='int'), interface=dict( default='public', choices=['public', 'internal', 'admin'], aliases=['endpoint_type']), - sdk_log_path=dict(default=None, type='str'), + sdk_log_path=dict(), sdk_log_level=dict( - default='INFO', type='str', choices=['INFO', 'DEBUG']), + default='INFO', choices=['INFO', 'DEBUG']), ) # Filter out all our custom parameters before passing to AnsibleModule kwargs_copy = copy.deepcopy(kwargs) @@ -345,7 +305,7 @@ class OpenStackModule: "The '%s' module has been renamed to '%s' in openstack " "collection: openstack.cloud.%s" % ( self.module_name, new_module_name, new_module_name), - version='2.0.0', collection_name='openstack.cloud') + version='3.0.0', collection_name='openstack.cloud') def openstack_cloud_from_module(self): """Sets up connection to cloud using provided options. Checks if all diff --git a/ansible_collections/openstack/cloud/plugins/module_utils/resource.py b/ansible_collections/openstack/cloud/plugins/module_utils/resource.py new file mode 100644 index 000000000..7f40de383 --- /dev/null +++ b/ansible_collections/openstack/cloud/plugins/module_utils/resource.py @@ -0,0 +1,237 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +# Copyright (c) 2023 Jakob Meng, <jakobmeng@web.de> +# Copyright (c) 2023 Red Hat, Inc. +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +class StateMachine: + + @staticmethod + def default_crud_functions(connection, service_name, type_name): + session = getattr(connection, service_name) + + create_function = getattr(session, 'create_{0}'.format(type_name)) + delete_function = getattr(session, 'delete_{0}'.format(type_name)) + find_function = getattr(session, 'find_{0}'.format(type_name)) + get_function = getattr(session, 'get_{0}'.format(type_name)) + list_function = getattr(session, '{0}s'.format(type_name)) + update_function = getattr(session, 'update_{0}'.format(type_name)) + + return dict( + create=create_function, + delete=delete_function, + find=find_function, + get=get_function, + list=list_function, + update=update_function, + ) + + def __init__(self, + connection, + sdk, + type_name, + service_name, + crud_functions=None, + **kwargs): + for k in ['connection', 'sdk', 'service_name', 'type_name']: + setattr(self, k, locals()[k]) + + self.session = getattr(connection, service_name) + + if not crud_functions: + crud_functions = StateMachine.default_crud_functions( + connection, service_name, type_name) + + for k in ['create', 'delete', 'find', 'get', 'list', 'update']: + setattr(self, '{0}_function'.format(k), crud_functions[k]) + + # kwargs is for passing arguments to subclasses + for k, v in kwargs.items(): + setattr(self, k, v) + + def __call__(self, attributes, check_mode, state, timeout, wait, + updateable_attributes, non_updateable_attributes, **kwargs): + # kwargs is for passing arguments to subclasses + + resource = self._find(attributes, **kwargs) + + if check_mode: + return self._simulate(state, resource, attributes, timeout, wait, + updateable_attributes, + non_updateable_attributes, **kwargs) + + if state == 'present' and not resource: + # Create resource + resource = self._create(attributes, timeout, wait, **kwargs) + return resource, True + + elif state == 'present' and resource: + # Update resource + update = self._build_update(resource, attributes, + updateable_attributes, + non_updateable_attributes, **kwargs) + if update: + resource = self._update(resource, timeout, update, wait, + **kwargs) + + return resource, bool(update) + + elif state == 'absent' and resource: + # Delete resource + self._delete(resource, attributes, timeout, wait, **kwargs) + return None, True + + elif state == 'absent' and not resource: + # Do nothing + return None, False + + def _build_update(self, resource, attributes, updateable_attributes, + non_updateable_attributes, **kwargs): + update = {} + + # Fetch details to populate all resource attributes + resource = self.get_function(resource['id']) + + comparison_attributes = ( + set(updateable_attributes + if updateable_attributes is not None + else attributes.keys()) + - set(non_updateable_attributes + if non_updateable_attributes is not None + else [])) + + resource_attributes = dict( + (k, attributes[k]) + for k in comparison_attributes + if not self._is_equal(attributes[k], resource[k])) + + if resource_attributes: + update['resource_attributes'] = resource_attributes + + return update + + def _create(self, attributes, timeout, wait, **kwargs): + resource = self.create_function(**attributes) + + if wait: + resource = self.sdk.resource.wait_for_status(self.session, + resource, + status='active', + failures=['error'], + wait=timeout, + attribute='status') + + return resource + + def _delete(self, resource, attributes, timeout, wait, **kwargs): + self.delete_function(resource['id']) + + if wait: + for count in self.sdk.utils.iterate_timeout( + timeout=timeout, + message="Timeout waiting for resource to be absent" + ): + if self._find(attributes) is None: + break + + def _freeze(self, o): + if isinstance(o, dict): + return frozenset((k, self._freeze(v)) for k, v in o.items()) + + if isinstance(o, list): + return tuple(self._freeze(v) for v in o) + + return o + + def _is_equal(self, a, b): + if any([a is None and b is not None, + a is not None and b is None]): + return False + + if a is None and b is None: + return True + + if isinstance(a, list) and isinstance(b, list): + return self._freeze(a) == self._freeze(b) + + if isinstance(a, dict) and isinstance(b, dict): + if set(a.keys()) != set(b.keys()): + return False + return self._freeze(a) == self._freeze(b) + + # else + return a == b + + def _find(self, attributes, **kwargs): + # use find_* functions for id instead of get_* functions because + # get_* functions raise exceptions when resources cannot be found + for k in ['id', 'name']: + if k in attributes: + return self.find_function(attributes[k]) + + matches = list(self._find_matches(attributes, **kwargs)) + if len(matches) > 1: + self.fail_json(msg='Found more than a single resource' + ' which matches the given attributes.') + elif len(matches) == 1: + return matches[0] + else: # len(matches) == 0 + return None + + def _find_matches(self, attributes, **kwargs): + return self.list_function(**attributes) + + def _update(self, resource, timeout, update, wait, **kwargs): + resource_attributes = update.get('resource_attributes') + if resource_attributes: + resource = self.update_function(resource['id'], + **resource_attributes) + + if wait: + resource = self.sdk.resource.wait_for_status(self.session, + resource, + status='active', + failures=['error'], + wait=timeout, + attribute='status') + + return resource + + def _simulate(self, state, resource, attributes, timeout, wait, + updateable_attributes, + non_updateable_attributes, **kwargs): + if state == 'present' and not resource: + resource = self._simulate_create(attributes, timeout, wait, + **kwargs) + return resource, True + elif state == 'present' and resource: + update = self._build_update(resource, attributes, + updateable_attributes, + non_updateable_attributes, + **kwargs) + if update: + resource = self._simulate_update(resource, timeout, update, + wait, **kwargs) + + return resource, bool(update) + elif state == 'absent' and resource: + return None, True + else: + # state == 'absent' and not resource: + return None, False + + def _simulate_create(self, attributes, timeout, wait, **kwargs): + class Resource(dict): + def to_dict(self, *args, **kwargs): + return self + + return Resource(attributes) + + def _simulate_update(self, resource, timeout, update, wait, **kwargs): + resource_attributes = update.get('resource_attributes') + if resource_attributes: + for k, v in resource_attributes.items(): + resource[k] = v + + return resource |