mirror of
				https://github.com/ansible-collections/community.general.git
				synced 2025-10-26 05:50:36 -07:00 
			
		
		
		
	
		
			
				
	
	
		
			351 lines
		
	
	
	
		
			11 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			351 lines
		
	
	
	
		
			11 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| #!/usr/bin/python
 | |
| # -*- coding: utf-8 -*-
 | |
| 
 | |
| # Copyright (c) 2018, Dag Wieers (dagwieers) <dag@wieers.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: cobbler_system
 | |
| short_description: Manage system objects in Cobbler
 | |
| description:
 | |
|   - Add, modify or remove systems in Cobbler
 | |
| extends_documentation_fragment:
 | |
|   - community.general.attributes
 | |
| attributes:
 | |
|   check_mode:
 | |
|     support: full
 | |
|   diff_mode:
 | |
|     support: full
 | |
| options:
 | |
|   host:
 | |
|     description:
 | |
|     - The name or IP address of the Cobbler system.
 | |
|     default: 127.0.0.1
 | |
|     type: str
 | |
|   port:
 | |
|     description:
 | |
|     - Port number to be used for REST connection.
 | |
|     - The default value depends on parameter O(use_ssl).
 | |
|     type: int
 | |
|   username:
 | |
|     description:
 | |
|     - The username to log in to Cobbler.
 | |
|     default: cobbler
 | |
|     type: str
 | |
|   password:
 | |
|     description:
 | |
|     - The password to log in to Cobbler.
 | |
|     type: str
 | |
|   use_ssl:
 | |
|     description:
 | |
|     - If V(false), an HTTP connection will be used instead of the default HTTPS connection.
 | |
|     type: bool
 | |
|     default: true
 | |
|   validate_certs:
 | |
|     description:
 | |
|     - If V(false), SSL certificates will not be validated.
 | |
|     - This should only set to V(false) when used on personally controlled sites using self-signed certificates.
 | |
|     type: bool
 | |
|     default: true
 | |
|   name:
 | |
|     description:
 | |
|     - The system name to manage.
 | |
|     type: str
 | |
|   properties:
 | |
|     description:
 | |
|     - A dictionary with system properties.
 | |
|     type: dict
 | |
|   interfaces:
 | |
|     description:
 | |
|     - A list of dictionaries containing interface options.
 | |
|     type: dict
 | |
|   sync:
 | |
|     description:
 | |
|     - Sync on changes.
 | |
|     - Concurrently syncing Cobbler is bound to fail.
 | |
|     type: bool
 | |
|     default: false
 | |
|   state:
 | |
|     description:
 | |
|     - Whether the system should be present, absent or a query is made.
 | |
|     choices: [ absent, present, query ]
 | |
|     default: present
 | |
|     type: str
 | |
| author:
 | |
| - Dag Wieers (@dagwieers)
 | |
| notes:
 | |
| - Concurrently syncing Cobbler is bound to fail with weird errors.
 | |
| - On python 2.7.8 and older (i.e. on RHEL7) you may need to tweak the python behaviour to disable certificate validation.
 | |
|   More information at L(Certificate verification in Python standard library HTTP clients,https://access.redhat.com/articles/2039753).
 | |
| '''
 | |
| 
 | |
| EXAMPLES = r'''
 | |
| - name: Ensure the system exists in Cobbler
 | |
|   community.general.cobbler_system:
 | |
|     host: cobbler01
 | |
|     username: cobbler
 | |
|     password: MySuperSecureP4sswOrd
 | |
|     name: myhost
 | |
|     properties:
 | |
|       profile: CentOS6-x86_64
 | |
|       name_servers: [ 2.3.4.5, 3.4.5.6 ]
 | |
|       name_servers_search: foo.com, bar.com
 | |
|     interfaces:
 | |
|       eth0:
 | |
|         macaddress: 00:01:02:03:04:05
 | |
|         ipaddress: 1.2.3.4
 | |
|   delegate_to: localhost
 | |
| 
 | |
| - name: Enable network boot in Cobbler
 | |
|   community.general.cobbler_system:
 | |
|     host: bdsol-aci-cobbler-01
 | |
|     username: cobbler
 | |
|     password: ins3965!
 | |
|     name: bdsol-aci51-apic1.cisco.com
 | |
|     properties:
 | |
|       netboot_enabled: true
 | |
|     state: present
 | |
|   delegate_to: localhost
 | |
| 
 | |
| - name: Query all systems in Cobbler
 | |
|   community.general.cobbler_system:
 | |
|     host: cobbler01
 | |
|     username: cobbler
 | |
|     password: MySuperSecureP4sswOrd
 | |
|     state: query
 | |
|   register: cobbler_systems
 | |
|   delegate_to: localhost
 | |
| 
 | |
| - name: Query a specific system in Cobbler
 | |
|   community.general.cobbler_system:
 | |
|     host: cobbler01
 | |
|     username: cobbler
 | |
|     password: MySuperSecureP4sswOrd
 | |
|     name: '{{ inventory_hostname }}'
 | |
|     state: query
 | |
|   register: cobbler_properties
 | |
|   delegate_to: localhost
 | |
| 
 | |
| - name: Ensure the system does not exist in Cobbler
 | |
|   community.general.cobbler_system:
 | |
|     host: cobbler01
 | |
|     username: cobbler
 | |
|     password: MySuperSecureP4sswOrd
 | |
|     name: myhost
 | |
|     state: absent
 | |
|   delegate_to: localhost
 | |
| '''
 | |
| 
 | |
| RETURN = r'''
 | |
| systems:
 | |
|   description: List of systems
 | |
|   returned: O(state=query) and O(name) is not provided
 | |
|   type: list
 | |
| system:
 | |
|   description: (Resulting) information about the system we are working with
 | |
|   returned: when O(name) is provided
 | |
|   type: dict
 | |
| '''
 | |
| 
 | |
| import ssl
 | |
| 
 | |
| from ansible.module_utils.basic import AnsibleModule
 | |
| from ansible.module_utils.six import iteritems
 | |
| from ansible.module_utils.six.moves import xmlrpc_client
 | |
| from ansible.module_utils.common.text.converters import to_text
 | |
| 
 | |
| from ansible_collections.community.general.plugins.module_utils.datetime import (
 | |
|     now,
 | |
| )
 | |
| 
 | |
| IFPROPS_MAPPING = dict(
 | |
|     bondingopts='bonding_opts',
 | |
|     bridgeopts='bridge_opts',
 | |
|     connected_mode='connected_mode',
 | |
|     cnames='cnames',
 | |
|     dhcptag='dhcp_tag',
 | |
|     dnsname='dns_name',
 | |
|     ifgateway='if_gateway',
 | |
|     interfacetype='interface_type',
 | |
|     interfacemaster='interface_master',
 | |
|     ipaddress='ip_address',
 | |
|     ipv6address='ipv6_address',
 | |
|     ipv6defaultgateway='ipv6_default_gateway',
 | |
|     ipv6mtu='ipv6_mtu',
 | |
|     ipv6prefix='ipv6_prefix',
 | |
|     ipv6secondaries='ipv6_secondariesu',
 | |
|     ipv6staticroutes='ipv6_static_routes',
 | |
|     macaddress='mac_address',
 | |
|     management='management',
 | |
|     mtu='mtu',
 | |
|     netmask='netmask',
 | |
|     static='static',
 | |
|     staticroutes='static_routes',
 | |
|     virtbridge='virt_bridge',
 | |
| )
 | |
| 
 | |
| 
 | |
| def getsystem(conn, name, token):
 | |
|     system = dict()
 | |
|     if name:
 | |
|         # system = conn.get_system(name, token)
 | |
|         systems = conn.find_system(dict(name=name), token)
 | |
|         if systems:
 | |
|             system = systems[0]
 | |
|     return system
 | |
| 
 | |
| 
 | |
| def main():
 | |
|     module = AnsibleModule(
 | |
|         argument_spec=dict(
 | |
|             host=dict(type='str', default='127.0.0.1'),
 | |
|             port=dict(type='int'),
 | |
|             username=dict(type='str', default='cobbler'),
 | |
|             password=dict(type='str', no_log=True),
 | |
|             use_ssl=dict(type='bool', default=True),
 | |
|             validate_certs=dict(type='bool', default=True),
 | |
|             name=dict(type='str'),
 | |
|             interfaces=dict(type='dict'),
 | |
|             properties=dict(type='dict'),
 | |
|             sync=dict(type='bool', default=False),
 | |
|             state=dict(type='str', default='present', choices=['absent', 'present', 'query']),
 | |
|         ),
 | |
|         supports_check_mode=True,
 | |
|     )
 | |
| 
 | |
|     username = module.params['username']
 | |
|     password = module.params['password']
 | |
|     port = module.params['port']
 | |
|     use_ssl = module.params['use_ssl']
 | |
|     validate_certs = module.params['validate_certs']
 | |
| 
 | |
|     name = module.params['name']
 | |
|     state = module.params['state']
 | |
| 
 | |
|     module.params['proto'] = 'https' if use_ssl else 'http'
 | |
|     if not port:
 | |
|         module.params['port'] = '443' if use_ssl else '80'
 | |
| 
 | |
|     result = dict(
 | |
|         changed=False,
 | |
|     )
 | |
| 
 | |
|     start = now()
 | |
| 
 | |
|     ssl_context = None
 | |
|     if not validate_certs:
 | |
|         try:
 | |
|             ssl_context = ssl._create_unverified_context()
 | |
|         except AttributeError:
 | |
|             # Legacy Python that doesn't verify HTTPS certificates by default
 | |
|             pass
 | |
|         else:
 | |
|             # Handle target environment that doesn't support HTTPS verification
 | |
|             ssl._create_default_https_context = ssl._create_unverified_context
 | |
| 
 | |
|     url = '{proto}://{host}:{port}/cobbler_api'.format(**module.params)
 | |
|     if ssl_context:
 | |
|         conn = xmlrpc_client.ServerProxy(url, context=ssl_context)
 | |
|     else:
 | |
|         conn = xmlrpc_client.Server(url)
 | |
| 
 | |
|     try:
 | |
|         token = conn.login(username, password)
 | |
|     except xmlrpc_client.Fault as e:
 | |
|         module.fail_json(msg="Failed to log in to Cobbler '{url}' as '{username}'. {error}".format(url=url, error=to_text(e), **module.params))
 | |
|     except Exception as e:
 | |
|         module.fail_json(msg="Connection to '{url}' failed. {error}".format(url=url, error=to_text(e), **module.params))
 | |
| 
 | |
|     system = getsystem(conn, name, token)
 | |
|     # result['system'] = system
 | |
| 
 | |
|     if state == 'query':
 | |
|         if name:
 | |
|             result['system'] = system
 | |
|         else:
 | |
|             # Turn it into a dictionary of dictionaries
 | |
|             # all_systems = conn.get_systems()
 | |
|             # result['systems'] = { system['name']: system for system in all_systems }
 | |
| 
 | |
|             # Return a list of dictionaries
 | |
|             result['systems'] = conn.get_systems()
 | |
| 
 | |
|     elif state == 'present':
 | |
| 
 | |
|         if system:
 | |
|             # Update existing entry
 | |
|             system_id = conn.get_system_handle(name, token)
 | |
| 
 | |
|             for key, value in iteritems(module.params['properties']):
 | |
|                 if key not in system:
 | |
|                     module.warn("Property '{0}' is not a valid system property.".format(key))
 | |
|                 if system[key] != value:
 | |
|                     try:
 | |
|                         conn.modify_system(system_id, key, value, token)
 | |
|                         result['changed'] = True
 | |
|                     except Exception as e:
 | |
|                         module.fail_json(msg="Unable to change '{0}' to '{1}'. {2}".format(key, value, e))
 | |
| 
 | |
|         else:
 | |
|             # Create a new entry
 | |
|             system_id = conn.new_system(token)
 | |
|             conn.modify_system(system_id, 'name', name, token)
 | |
|             result['changed'] = True
 | |
| 
 | |
|             if module.params['properties']:
 | |
|                 for key, value in iteritems(module.params['properties']):
 | |
|                     try:
 | |
|                         conn.modify_system(system_id, key, value, token)
 | |
|                     except Exception as e:
 | |
|                         module.fail_json(msg="Unable to change '{0}' to '{1}'. {2}".format(key, value, e))
 | |
| 
 | |
|         # Add interface properties
 | |
|         interface_properties = dict()
 | |
|         if module.params['interfaces']:
 | |
|             for device, values in iteritems(module.params['interfaces']):
 | |
|                 for key, value in iteritems(values):
 | |
|                     if key == 'name':
 | |
|                         continue
 | |
|                     if key not in IFPROPS_MAPPING:
 | |
|                         module.warn("Property '{0}' is not a valid system property.".format(key))
 | |
|                     if not system or system['interfaces'][device][IFPROPS_MAPPING[key]] != value:
 | |
|                         result['changed'] = True
 | |
|                     interface_properties['{0}-{1}'.format(key, device)] = value
 | |
| 
 | |
|             if result['changed'] is True:
 | |
|                 conn.modify_system(system_id, "modify_interface", interface_properties, token)
 | |
| 
 | |
|         # Only save when the entry was changed
 | |
|         if not module.check_mode and result['changed']:
 | |
|             conn.save_system(system_id, token)
 | |
| 
 | |
|     elif state == 'absent':
 | |
| 
 | |
|         if system:
 | |
|             if not module.check_mode:
 | |
|                 conn.remove_system(name, token)
 | |
|             result['changed'] = True
 | |
| 
 | |
|     if not module.check_mode and module.params['sync'] and result['changed']:
 | |
|         try:
 | |
|             conn.sync(token)
 | |
|         except Exception as e:
 | |
|             module.fail_json(msg="Failed to sync Cobbler. {0}".format(to_text(e)))
 | |
| 
 | |
|     if state in ('absent', 'present'):
 | |
|         result['system'] = getsystem(conn, name, token)
 | |
| 
 | |
|         if module._diff:
 | |
|             result['diff'] = dict(before=system, after=result['system'])
 | |
| 
 | |
|     elapsed = now() - start
 | |
|     module.exit_json(elapsed=elapsed.seconds, **result)
 | |
| 
 | |
| 
 | |
| if __name__ == '__main__':
 | |
|     main()
 |