mirror of
				https://github.com/ansible-collections/community.general.git
				synced 2025-10-25 13:34:01 -07:00 
			
		
		
		
	Use semantic markup (modules r-s) (#6683)
* Use semantic markup.
* Use 'ignore:' for alias reference.
* Ignore sanity errors for older ansible-core versions.
* Improve markup for RHSM modules.
Co-authored-by: Pino Toscano <ptoscano@redhat.com>
* 'ignore:' is no longer needed.
* E() now works better.
---------
Co-authored-by: Pino Toscano <ptoscano@redhat.com>
(cherry picked from commit eff0cb0ed9)
Co-authored-by: Felix Fontein <felix@fontein.de>
		
	
			
		
			
				
	
	
		
			336 lines
		
	
	
	
		
			11 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			336 lines
		
	
	
	
		
			11 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| #!/usr/bin/python
 | |
| # -*- coding: utf-8 -*-
 | |
| 
 | |
| # Copyright (c) 2016, Renato Orgito <orgito@gmail.com>
 | |
| # GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
 | |
| # SPDX-License-Identifier: GPL-3.0-or-later
 | |
| 
 | |
| from __future__ import absolute_import, division, print_function
 | |
| __metaclass__ = type
 | |
| 
 | |
| 
 | |
| DOCUMENTATION = r'''
 | |
| ---
 | |
| module: spectrum_device
 | |
| short_description: Creates/deletes devices in CA Spectrum
 | |
| description:
 | |
|     - This module allows you to create and delete devices in CA Spectrum U(https://www.ca.com/us/products/ca-spectrum.html).
 | |
|     - Tested on CA Spectrum 9.4.2, 10.1.1 and 10.2.1
 | |
| author: "Renato Orgito (@orgito)"
 | |
| extends_documentation_fragment:
 | |
|     - community.general.attributes
 | |
| attributes:
 | |
|     check_mode:
 | |
|         support: full
 | |
|     diff_mode:
 | |
|         support: none
 | |
| options:
 | |
|     device:
 | |
|         type: str
 | |
|         aliases: [ host, name ]
 | |
|         required: true
 | |
|         description:
 | |
|             - IP address of the device.
 | |
|             - If a hostname is given, it will be resolved to the IP address.
 | |
|     community:
 | |
|         type: str
 | |
|         description:
 | |
|             - SNMP community used for device discovery.
 | |
|             - Required when O(state=present).
 | |
|         required: true
 | |
|     landscape:
 | |
|         type: str
 | |
|         required: true
 | |
|         description:
 | |
|             - Landscape handle of the SpectroServer to which add or remove the device.
 | |
|     state:
 | |
|         type: str
 | |
|         description:
 | |
|             - On V(present) creates the device when it does not exist.
 | |
|             - On V(absent) removes the device when it exists.
 | |
|         choices: ['present', 'absent']
 | |
|         default: 'present'
 | |
|     url:
 | |
|         type: str
 | |
|         aliases: [ oneclick_url ]
 | |
|         required: true
 | |
|         description:
 | |
|             - HTTP, HTTPS URL of the Oneclick server in the form V((http|https\)://host.domain[:port]).
 | |
|     url_username:
 | |
|         type: str
 | |
|         aliases: [ oneclick_user ]
 | |
|         required: true
 | |
|         description:
 | |
|             - Oneclick user name.
 | |
|     url_password:
 | |
|         type: str
 | |
|         aliases: [ oneclick_password ]
 | |
|         required: true
 | |
|         description:
 | |
|             - Oneclick user password.
 | |
|     use_proxy:
 | |
|         description:
 | |
|             - if V(false), it will not use a proxy, even if one is defined in an environment variable on the target hosts.
 | |
|         default: true
 | |
|         type: bool
 | |
|     validate_certs:
 | |
|         description:
 | |
|             - If V(false), SSL certificates will not be validated. This should only be used
 | |
|               on personally controlled sites using self-signed certificates.
 | |
|         default: true
 | |
|         type: bool
 | |
|     agentport:
 | |
|         type: int
 | |
|         required: false
 | |
|         description:
 | |
|             - UDP port used for SNMP discovery.
 | |
|         default: 161
 | |
| notes:
 | |
|    -  The devices will be created inside the I(Universe) container of the specified landscape.
 | |
|    -  All the operations will be performed only on the specified landscape.
 | |
| '''
 | |
| 
 | |
| EXAMPLES = '''
 | |
| - name: Add device to CA Spectrum
 | |
|   local_action:
 | |
|     module: spectrum_device
 | |
|     device: '{{ ansible_host }}'
 | |
|     community: secret
 | |
|     landscape: '0x100000'
 | |
|     oneclick_url: http://oneclick.example.com:8080
 | |
|     oneclick_user: username
 | |
|     oneclick_password: password
 | |
|     state: present
 | |
| 
 | |
| 
 | |
| - name: Remove device from CA Spectrum
 | |
|   local_action:
 | |
|     module: spectrum_device
 | |
|     device: '{{ ansible_host }}'
 | |
|     landscape: '{{ landscape_handle }}'
 | |
|     oneclick_url: http://oneclick.example.com:8080
 | |
|     oneclick_user: username
 | |
|     oneclick_password: password
 | |
|     use_proxy: false
 | |
|     state: absent
 | |
| '''
 | |
| 
 | |
| RETURN = '''
 | |
| device:
 | |
|   description: device data when state = present
 | |
|   returned: success
 | |
|   type: dict
 | |
|   sample: {'model_handle': '0x1007ab', 'landscape': '0x100000', 'address': '10.10.5.1'}
 | |
| '''
 | |
| 
 | |
| from socket import gethostbyname, gaierror
 | |
| import xml.etree.ElementTree as ET
 | |
| 
 | |
| from ansible.module_utils.basic import AnsibleModule
 | |
| from ansible.module_utils.urls import fetch_url
 | |
| 
 | |
| 
 | |
| def request(resource, xml=None, method=None):
 | |
|     headers = {
 | |
|         "Content-Type": "application/xml",
 | |
|         "Accept": "application/xml"
 | |
|     }
 | |
| 
 | |
|     url = module.params['oneclick_url'] + '/spectrum/restful/' + resource
 | |
| 
 | |
|     response, info = fetch_url(module, url, data=xml, method=method, headers=headers, timeout=45)
 | |
| 
 | |
|     if info['status'] == 401:
 | |
|         module.fail_json(msg="failed to authenticate to Oneclick server")
 | |
| 
 | |
|     if info['status'] not in (200, 201, 204):
 | |
|         module.fail_json(msg=info['msg'])
 | |
| 
 | |
|     return response.read()
 | |
| 
 | |
| 
 | |
| def post(resource, xml=None):
 | |
|     return request(resource, xml=xml, method='POST')
 | |
| 
 | |
| 
 | |
| def delete(resource):
 | |
|     return request(resource, xml=None, method='DELETE')
 | |
| 
 | |
| 
 | |
| def get_ip():
 | |
|     try:
 | |
|         device_ip = gethostbyname(module.params.get('device'))
 | |
|     except gaierror:
 | |
|         module.fail_json(msg="failed to resolve device ip address for '%s'" % module.params.get('device'))
 | |
| 
 | |
|     return device_ip
 | |
| 
 | |
| 
 | |
| def get_device(device_ip):
 | |
|     """Query OneClick for the device using the IP Address"""
 | |
|     resource = '/models'
 | |
|     landscape_min = "0x%x" % int(module.params.get('landscape'), 16)
 | |
|     landscape_max = "0x%x" % (int(module.params.get('landscape'), 16) + 0x100000)
 | |
| 
 | |
|     xml = """<?xml version="1.0" encoding="UTF-8"?>
 | |
|         <rs:model-request throttlesize="5"
 | |
|         xmlns:rs="http://www.ca.com/spectrum/restful/schema/request"
 | |
|         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 | |
|         xsi:schemaLocation="http://www.ca.com/spectrum/restful/schema/request ../../../xsd/Request.xsd">
 | |
|             <rs:target-models>
 | |
|             <rs:models-search>
 | |
|                 <rs:search-criteria xmlns="http://www.ca.com/spectrum/restful/schema/filter">
 | |
|                     <action-models>
 | |
|                         <filtered-models>
 | |
|                             <and>
 | |
|                                 <equals>
 | |
|                                     <model-type>SearchManager</model-type>
 | |
|                                 </equals>
 | |
|                                 <greater-than>
 | |
|                                     <attribute id="0x129fa">
 | |
|                                         <value>{mh_min}</value>
 | |
|                                     </attribute>
 | |
|                                 </greater-than>
 | |
|                                 <less-than>
 | |
|                                     <attribute id="0x129fa">
 | |
|                                         <value>{mh_max}</value>
 | |
|                                     </attribute>
 | |
|                                 </less-than>
 | |
|                             </and>
 | |
|                         </filtered-models>
 | |
|                         <action>FIND_DEV_MODELS_BY_IP</action>
 | |
|                         <attribute id="AttributeID.NETWORK_ADDRESS">
 | |
|                             <value>{search_ip}</value>
 | |
|                         </attribute>
 | |
|                     </action-models>
 | |
|                 </rs:search-criteria>
 | |
|             </rs:models-search>
 | |
|             </rs:target-models>
 | |
|             <rs:requested-attribute id="0x12d7f" /> <!--Network Address-->
 | |
|         </rs:model-request>
 | |
|         """.format(search_ip=device_ip, mh_min=landscape_min, mh_max=landscape_max)
 | |
| 
 | |
|     result = post(resource, xml=xml)
 | |
| 
 | |
|     root = ET.fromstring(result)
 | |
| 
 | |
|     if root.get('total-models') == '0':
 | |
|         return None
 | |
| 
 | |
|     namespace = dict(ca='http://www.ca.com/spectrum/restful/schema/response')
 | |
| 
 | |
|     # get the first device
 | |
|     model = root.find('ca:model-responses', namespace).find('ca:model', namespace)
 | |
| 
 | |
|     if model.get('error'):
 | |
|         module.fail_json(msg="error checking device: %s" % model.get('error'))
 | |
| 
 | |
|     # get the attributes
 | |
|     model_handle = model.get('mh')
 | |
| 
 | |
|     model_address = model.find('./*[@id="0x12d7f"]').text
 | |
| 
 | |
|     # derive the landscape handler from the model handler of the device
 | |
|     model_landscape = "0x%x" % int(int(model_handle, 16) // 0x100000 * 0x100000)
 | |
| 
 | |
|     device = dict(
 | |
|         model_handle=model_handle,
 | |
|         address=model_address,
 | |
|         landscape=model_landscape)
 | |
| 
 | |
|     return device
 | |
| 
 | |
| 
 | |
| def add_device():
 | |
|     device_ip = get_ip()
 | |
|     device = get_device(device_ip)
 | |
| 
 | |
|     if device:
 | |
|         module.exit_json(changed=False, device=device)
 | |
| 
 | |
|     if module.check_mode:
 | |
|         device = dict(
 | |
|             model_handle=None,
 | |
|             address=device_ip,
 | |
|             landscape="0x%x" % int(module.params.get('landscape'), 16))
 | |
|         module.exit_json(changed=True, device=device)
 | |
| 
 | |
|     resource = 'model?ipaddress=' + device_ip + '&commstring=' + module.params.get('community')
 | |
|     resource += '&landscapeid=' + module.params.get('landscape')
 | |
| 
 | |
|     if module.params.get('agentport', None):
 | |
|         resource += '&agentport=' + str(module.params.get('agentport', 161))
 | |
| 
 | |
|     result = post(resource)
 | |
|     root = ET.fromstring(result)
 | |
| 
 | |
|     if root.get('error') != 'Success':
 | |
|         module.fail_json(msg=root.get('error-message'))
 | |
| 
 | |
|     namespace = dict(ca='http://www.ca.com/spectrum/restful/schema/response')
 | |
|     model = root.find('ca:model', namespace)
 | |
| 
 | |
|     model_handle = model.get('mh')
 | |
|     model_landscape = "0x%x" % int(int(model_handle, 16) // 0x100000 * 0x100000)
 | |
| 
 | |
|     device = dict(
 | |
|         model_handle=model_handle,
 | |
|         address=device_ip,
 | |
|         landscape=model_landscape,
 | |
|     )
 | |
| 
 | |
|     module.exit_json(changed=True, device=device)
 | |
| 
 | |
| 
 | |
| def remove_device():
 | |
|     device_ip = get_ip()
 | |
|     device = get_device(device_ip)
 | |
| 
 | |
|     if device is None:
 | |
|         module.exit_json(changed=False)
 | |
| 
 | |
|     if module.check_mode:
 | |
|         module.exit_json(changed=True)
 | |
| 
 | |
|     resource = '/model/' + device['model_handle']
 | |
|     result = delete(resource)
 | |
| 
 | |
|     root = ET.fromstring(result)
 | |
| 
 | |
|     namespace = dict(ca='http://www.ca.com/spectrum/restful/schema/response')
 | |
|     error = root.find('ca:error', namespace).text
 | |
| 
 | |
|     if error != 'Success':
 | |
|         error_message = root.find('ca:error-message', namespace).text
 | |
|         module.fail_json(msg="%s %s" % (error, error_message))
 | |
| 
 | |
|     module.exit_json(changed=True)
 | |
| 
 | |
| 
 | |
| def main():
 | |
|     global module
 | |
|     module = AnsibleModule(
 | |
|         argument_spec=dict(
 | |
|             device=dict(required=True, aliases=['host', 'name']),
 | |
|             landscape=dict(required=True),
 | |
|             state=dict(choices=['present', 'absent'], default='present'),
 | |
|             community=dict(required=True, no_log=True),   # @TODO remove the 'required', given the required_if ?
 | |
|             agentport=dict(type='int', default=161),
 | |
|             url=dict(required=True, aliases=['oneclick_url']),
 | |
|             url_username=dict(required=True, aliases=['oneclick_user']),
 | |
|             url_password=dict(required=True, no_log=True, aliases=['oneclick_password']),
 | |
|             use_proxy=dict(type='bool', default=True),
 | |
|             validate_certs=dict(type='bool', default=True),
 | |
|         ),
 | |
|         required_if=[('state', 'present', ['community'])],
 | |
|         supports_check_mode=True
 | |
|     )
 | |
| 
 | |
|     if module.params.get('state') == 'present':
 | |
|         add_device()
 | |
|     else:
 | |
|         remove_device()
 | |
| 
 | |
| 
 | |
| if __name__ == '__main__':
 | |
|     main()
 |