1#!/usr/local/bin/python3.8 2# -*- coding: utf-8 -*- 3# 4# Copyright (c) 2016 Red Hat, Inc. 5# 6# This file is part of Ansible 7# 8# Ansible is free software: you can redistribute it and/or modify 9# it under the terms of the GNU General Public License as published by 10# the Free Software Foundation, either version 3 of the License, or 11# (at your option) any later version. 12# 13# Ansible is distributed in the hope that it will be useful, 14# but WITHOUT ANY WARRANTY; without even the implied warranty of 15# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16# GNU General Public License for more details. 17# 18# You should have received a copy of the GNU General Public License 19# along with Ansible. If not, see <http://www.gnu.org/licenses/>. 20# 21 22from __future__ import (absolute_import, division, print_function) 23__metaclass__ = type 24 25DOCUMENTATION = ''' 26--- 27module: ovirt_external_provider 28short_description: Module to manage external providers in oVirt/RHV 29version_added: "1.0.0" 30author: "Ondra Machacek (@machacekondra)" 31description: 32 - "Module to manage external providers in oVirt/RHV" 33options: 34 name: 35 description: 36 - "Name of the external provider to manage." 37 type: str 38 state: 39 description: 40 - "Should the external be present or absent" 41 - "When you are using absent for I(os_volume), you need to make 42 sure that SD is not attached to the data center!" 43 choices: ['present', 'absent'] 44 default: present 45 type: str 46 description: 47 description: 48 - "Description of the external provider." 49 type: str 50 type: 51 description: 52 - "Type of the external provider." 53 choices: ['os_image', 'network', 'os_volume', 'foreman'] 54 required: true 55 type: str 56 aliases: ['provider'] 57 url: 58 description: 59 - "URL where external provider is hosted." 60 - "Applicable for those types: I(os_image), I(os_volume), I(network) and I(foreman)." 61 type: str 62 username: 63 description: 64 - "Username to be used for login to external provider." 65 - "Applicable for all types." 66 type: str 67 password: 68 description: 69 - "Password of the user specified in C(username) parameter." 70 - "Applicable for all types." 71 type: str 72 tenant_name: 73 description: 74 - "Name of the tenant." 75 - "Applicable for those types: I(os_image), I(os_volume) and I(network)." 76 aliases: ['tenant'] 77 type: str 78 authentication_url: 79 description: 80 - "Keystone authentication URL of the openstack provider." 81 - "Applicable for those types: I(os_image), I(os_volume) and I(network)." 82 aliases: ['auth_url'] 83 type: str 84 data_center: 85 description: 86 - "Name of the data center where provider should be attached." 87 - "Applicable for those type: I(os_volume)." 88 type: str 89 read_only: 90 description: 91 - "Specify if the network should be read only." 92 - "Applicable if C(type) is I(network)." 93 type: bool 94 network_type: 95 description: 96 - "Type of the external network provider either external (for example OVN) or neutron." 97 - "Applicable if C(type) is I(network)." 98 choices: ['external', 'neutron'] 99 default: 'external' 100 type: str 101 authentication_keys: 102 description: 103 - "List of authentication keys." 104 - "When you will not pass these keys and there are already some 105 of them defined in the system they will be removed." 106 - "Applicable for I(os_volume)." 107 suboptions: 108 uuid: 109 description: 110 - The uuid which will be used. 111 value: 112 description: 113 - The value which will be used. 114 default: [] 115 type: list 116 elements: dict 117 aliases: ['auth_keys'] 118extends_documentation_fragment: ovirt.ovirt.ovirt 119''' 120 121EXAMPLES = ''' 122# Examples don't contain auth parameter for simplicity, 123# look at ovirt_auth module to see how to reuse authentication: 124 125# Add image external provider: 126- ovirt.ovirt.ovirt_external_provider: 127 name: image_provider 128 type: os_image 129 url: http://1.2.3.4:9292 130 username: admin 131 password: 123456 132 tenant: admin 133 auth_url: http://1.2.3.4:35357/v2.0 134 135# Add volume external provider: 136- ovirt.ovirt.ovirt_external_provider: 137 name: image_provider 138 type: os_volume 139 url: http://1.2.3.4:9292 140 username: admin 141 password: 123456 142 tenant: admin 143 auth_url: http://1.2.3.4:5000/v2.0 144 authentication_keys: 145 - 146 uuid: "1234567-a1234-12a3-a234-123abc45678" 147 value: "ABCD00000000111111222333445w==" 148 149# Add foreman provider: 150- ovirt.ovirt.ovirt_external_provider: 151 name: foreman_provider 152 type: foreman 153 url: https://foreman.example.com 154 username: admin 155 password: 123456 156 157# Add external network provider for OVN: 158- ovirt.ovirt.ovirt_external_provider: 159 name: ovn_provider 160 type: network 161 network_type: external 162 url: http://1.2.3.4:9696 163 164# Remove image external provider: 165- ovirt.ovirt.ovirt_external_provider: 166 state: absent 167 name: image_provider 168 type: os_image 169''' 170 171RETURN = ''' 172id: 173 description: ID of the external provider which is managed 174 returned: On success if external provider is found. 175 type: str 176 sample: 7de90f31-222c-436c-a1ca-7e655bd5b60c 177external_host_provider: 178 description: "Dictionary of all the external_host_provider attributes. External provider attributes can be found on your oVirt/RHV instance 179 at following url: http://ovirt.github.io/ovirt-engine-api-model/master/#types/external_host_provider." 180 returned: "On success and if parameter 'type: foreman' is used." 181 type: dict 182openstack_image_provider: 183 description: "Dictionary of all the openstack_image_provider attributes. External provider attributes can be found on your oVirt/RHV instance 184 at following url: http://ovirt.github.io/ovirt-engine-api-model/master/#types/openstack_image_provider." 185 returned: "On success and if parameter 'type: os_image' is used." 186 type: dict 187openstack_volume_provider: 188 description: "Dictionary of all the openstack_volume_provider attributes. External provider attributes can be found on your oVirt/RHV instance 189 at following url: http://ovirt.github.io/ovirt-engine-api-model/master/#types/openstack_volume_provider." 190 returned: "On success and if parameter 'type: os_volume' is used." 191 type: dict 192openstack_network_provider: 193 description: "Dictionary of all the openstack_network_provider attributes. External provider attributes can be found on your oVirt/RHV instance 194 at following url: http://ovirt.github.io/ovirt-engine-api-model/master/#types/openstack_network_provider." 195 returned: "On success and if parameter 'type: network' is used." 196 type: dict 197''' 198 199import traceback 200 201try: 202 import ovirtsdk4.types as otypes 203except ImportError: 204 pass 205 206from ansible.module_utils.basic import AnsibleModule 207from ansible_collections.ovirt.ovirt.plugins.module_utils.ovirt import ( 208 BaseModule, 209 check_params, 210 check_sdk, 211 create_connection, 212 equal, 213 ovirt_full_argument_spec, 214) 215 216 217OS_VOLUME = 'os_volume' 218OS_IMAGE = 'os_image' 219NETWORK = 'network' 220FOREMAN = 'foreman' 221 222 223class ExternalProviderModule(BaseModule): 224 225 non_provider_params = ['type', 'authentication_keys', 'data_center'] 226 227 def provider_type(self, provider_type): 228 self._provider_type = provider_type 229 230 def provider_module_params(self): 231 provider_params = [ 232 (key, value) for key, value in self._module.params.items() if key 233 not in self.non_provider_params 234 ] 235 provider_params.append(('data_center', self.get_data_center())) 236 return provider_params 237 238 def get_data_center(self): 239 dc_name = self._module.params.get("data_center", None) 240 if dc_name: 241 system_service = self._connection.system_service() 242 data_centers_service = system_service.data_centers_service() 243 return data_centers_service.list( 244 search='name=%s' % dc_name, 245 )[0] 246 return dc_name 247 248 def build_entity(self): 249 provider_type = self._provider_type( 250 requires_authentication=self._module.params.get('username') is not None, 251 ) 252 if self._module.params.pop('type') == NETWORK: 253 setattr( 254 provider_type, 255 'type', 256 otypes.OpenStackNetworkProviderType(self._module.params.pop('network_type')) 257 ) 258 259 for key, value in self.provider_module_params(): 260 if hasattr(provider_type, key): 261 setattr(provider_type, key, value) 262 263 return provider_type 264 265 def update_check(self, entity): 266 return ( 267 equal(self._module.params.get('description'), entity.description) and 268 equal(self._module.params.get('url'), entity.url) and 269 equal(self._module.params.get('authentication_url'), entity.authentication_url) and 270 equal(self._module.params.get('tenant_name'), getattr(entity, 'tenant_name', None)) and 271 equal(self._module.params.get('username'), entity.username) 272 ) 273 274 def update_volume_provider_auth_keys( 275 self, provider, providers_service, keys 276 ): 277 """ 278 Update auth keys for volume provider, if not exist add them or remove 279 if they are not specified and there are already defined in the external 280 volume provider. 281 282 Args: 283 provider (dict): Volume provider details. 284 providers_service (openstack_volume_providers_service): Provider 285 service. 286 keys (list): Keys to be updated/added to volume provider, each key 287 is represented as dict with keys: uuid, value. 288 """ 289 290 provider_service = providers_service.provider_service(provider['id']) 291 auth_keys_service = provider_service.authentication_keys_service() 292 provider_keys = auth_keys_service.list() 293 # removing keys which are not defined 294 for key in [ 295 k.id for k in provider_keys if k.uuid not in [ 296 defined_key['uuid'] for defined_key in keys 297 ] 298 ]: 299 self.changed = True 300 if not self._module.check_mode: 301 auth_keys_service.key_service(key).remove() 302 if not (provider_keys or keys): 303 # Nothing need to do when both are empty. 304 return 305 for key in keys: 306 key_id_for_update = None 307 for existing_key in provider_keys: 308 if key['uuid'] == existing_key.uuid: 309 key_id_for_update = existing_key.id 310 311 auth_key_usage_type = ( 312 otypes.OpenstackVolumeAuthenticationKeyUsageType("ceph") 313 ) 314 auth_key = otypes.OpenstackVolumeAuthenticationKey( 315 usage_type=auth_key_usage_type, 316 uuid=key['uuid'], 317 value=key['value'], 318 ) 319 320 if not key_id_for_update: 321 self.changed = True 322 if not self._module.check_mode: 323 auth_keys_service.add(auth_key) 324 else: 325 # We cannot really distinguish here if it was really updated cause 326 # we cannot take key value to check if it was changed or not. So 327 # for sure we update here always. 328 self.changed = True 329 if not self._module.check_mode: 330 auth_key_service = ( 331 auth_keys_service.key_service(key_id_for_update) 332 ) 333 auth_key_service.update(auth_key) 334 335 336def _external_provider_service(provider_type, system_service): 337 if provider_type == OS_IMAGE: 338 return otypes.OpenStackImageProvider, system_service.openstack_image_providers_service() 339 elif provider_type == NETWORK: 340 return otypes.OpenStackNetworkProvider, system_service.openstack_network_providers_service() 341 elif provider_type == OS_VOLUME: 342 return otypes.OpenStackVolumeProvider, system_service.openstack_volume_providers_service() 343 elif provider_type == FOREMAN: 344 return otypes.ExternalHostProvider, system_service.external_host_providers_service() 345 346 347def main(): 348 argument_spec = ovirt_full_argument_spec( 349 state=dict( 350 choices=['present', 'absent'], 351 default='present', 352 ), 353 name=dict(default=None), 354 description=dict(default=None), 355 type=dict( 356 required=True, 357 choices=[ 358 OS_IMAGE, NETWORK, OS_VOLUME, FOREMAN, 359 ], 360 aliases=['provider'], 361 ), 362 url=dict(default=None), 363 username=dict(default=None), 364 password=dict(default=None, no_log=True), 365 tenant_name=dict(default=None, aliases=['tenant']), 366 authentication_url=dict(default=None, aliases=['auth_url']), 367 data_center=dict(default=None), 368 read_only=dict(default=None, type='bool'), 369 network_type=dict( 370 default='external', 371 choices=['external', 'neutron'], 372 ), 373 authentication_keys=dict( 374 default=[], aliases=['auth_keys'], type='list', no_log=True, elements='dict' 375 ), 376 ) 377 module = AnsibleModule( 378 argument_spec=argument_spec, 379 supports_check_mode=True, 380 ) 381 382 check_sdk(module) 383 check_params(module) 384 385 try: 386 auth = module.params.pop('auth') 387 connection = create_connection(auth) 388 provider_type_param = module.params.get('type') 389 provider_type, external_providers_service = _external_provider_service( 390 provider_type=provider_type_param, 391 system_service=connection.system_service(), 392 ) 393 external_providers_module = ExternalProviderModule( 394 connection=connection, 395 module=module, 396 service=external_providers_service, 397 ) 398 external_providers_module.provider_type(provider_type) 399 400 state = module.params.pop('state') 401 if state == 'absent': 402 ret = external_providers_module.remove() 403 elif state == 'present': 404 ret = external_providers_module.create() 405 openstack_volume_provider_id = ret.get('id') 406 if ( 407 provider_type_param == OS_VOLUME and 408 openstack_volume_provider_id 409 ): 410 external_providers_module.update_volume_provider_auth_keys( 411 ret, external_providers_service, 412 module.params.get('authentication_keys'), 413 ) 414 415 module.exit_json(**ret) 416 417 except Exception as e: 418 module.fail_json(msg=str(e), exception=traceback.format_exc()) 419 finally: 420 connection.close(logout=auth.get('token') is None) 421 422 423if __name__ == "__main__": 424 main() 425