mirror of
https://github.com/ansible-collections/community.general.git
synced 2024-09-14 20:13:21 +02:00
470 lines
16 KiB
Python
470 lines
16 KiB
Python
#!/usr/bin/python
|
|
# Copyright: Ansible Project
|
|
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
|
|
|
from __future__ import absolute_import, division, print_function
|
|
__metaclass__ = type
|
|
|
|
|
|
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
|
'status': ['preview'],
|
|
'supported_by': 'community'}
|
|
|
|
DOCUMENTATION = '''
|
|
---
|
|
module: icx_system
|
|
author: "Ruckus Wireless (@Commscope)"
|
|
short_description: Manage the system attributes on Ruckus ICX 7000 series switches
|
|
description:
|
|
- This module provides declarative management of node system attributes
|
|
on Ruckus ICX 7000 series switches. It provides an option to configure host system
|
|
parameters or remove those parameters from the device active
|
|
configuration.
|
|
notes:
|
|
- Tested against ICX 10.1.
|
|
- For information on using ICX platform, see L(the ICX OS Platform Options guide,../network/user_guide/platform_icx.html).
|
|
options:
|
|
hostname:
|
|
description:
|
|
- Configure the device hostname parameter. This option takes an ASCII string value.
|
|
type: str
|
|
domain_name:
|
|
description:
|
|
- Configure the IP domain name on the remote device to the provided value.
|
|
Value should be in the dotted name form and
|
|
will be appended to the hostname to create a fully-qualified domain name.
|
|
type: list
|
|
domain_search:
|
|
description:
|
|
- Provides the list of domain names to
|
|
append to the hostname for the purpose of doing name resolution.
|
|
This argument accepts a list of names and will be reconciled
|
|
with the current active configuration on the running node.
|
|
type: list
|
|
name_servers:
|
|
description:
|
|
- List of DNS name servers by IP address to use to perform name resolution
|
|
lookups.
|
|
type: list
|
|
aaa_servers:
|
|
description:
|
|
- Configures radius/tacacs server
|
|
type: list
|
|
suboptions:
|
|
type:
|
|
description:
|
|
- specify the type of the server
|
|
type: str
|
|
choices: ['radius','tacacs']
|
|
hostname:
|
|
description:
|
|
- Configures the host name of the RADIUS server
|
|
type: str
|
|
auth_port_type:
|
|
description:
|
|
- specifies the type of the authentication port
|
|
type: str
|
|
choices: ['auth-port']
|
|
auth_port_num:
|
|
description:
|
|
- Configures the authentication UDP port. The default value is 1812.
|
|
type: str
|
|
acct_port_num:
|
|
description:
|
|
- Configures the accounting UDP port. The default value is 1813.
|
|
type: str
|
|
acct_type:
|
|
description:
|
|
- Usage of the accounting port.
|
|
type: str
|
|
choices: ['accounting-only', 'authentication-only','authorization-only', default]
|
|
auth_key:
|
|
description:
|
|
- Configure the key for the server
|
|
type: str
|
|
auth_key_type:
|
|
description:
|
|
- List of authentication level specified in the choices
|
|
type: list
|
|
choices: ['dot1x','mac-auth','web-auth']
|
|
state:
|
|
description:
|
|
- State of the configuration
|
|
values in the device's current active configuration. When set
|
|
to I(present), the values should be configured in the device active
|
|
configuration and when set to I(absent) the values should not be
|
|
in the device active configuration
|
|
type: str
|
|
default: present
|
|
choices: ['present', 'absent']
|
|
check_running_config:
|
|
description:
|
|
- Check running configuration. This can be set as environment variable.
|
|
Module will use environment variable value(default:True), unless it is overridden, by specifying it as module parameter.
|
|
type: bool
|
|
default: yes
|
|
'''
|
|
|
|
EXAMPLES = """
|
|
- name: configure hostname and domain name
|
|
icx_system:
|
|
hostname: icx
|
|
domain_search:
|
|
- ansible.com
|
|
- redhat.com
|
|
- ruckus.com
|
|
|
|
- name: configure radius server of type auth-port
|
|
icx_system:
|
|
aaa_servers:
|
|
- type: radius
|
|
hostname: radius-server
|
|
auth_port_type: auth-port
|
|
auth_port_num: 1821
|
|
acct_port_num: 1321
|
|
acct_type: accounting-only
|
|
auth_key: abc
|
|
auth_key_type:
|
|
- dot1x
|
|
- mac-auth
|
|
|
|
- name: configure tacacs server
|
|
icx_system:
|
|
aaa_servers:
|
|
- type: tacacs
|
|
hostname: tacacs-server
|
|
auth_port_type: auth-port
|
|
auth_port_num: 1821
|
|
acct_port_num: 1321
|
|
acct_type: accounting-only
|
|
auth_key: xyz
|
|
|
|
- name: configure name servers
|
|
icx_system:
|
|
name_servers:
|
|
- 8.8.8.8
|
|
- 8.8.4.4
|
|
"""
|
|
|
|
RETURN = """
|
|
commands:
|
|
description: The list of configuration mode commands to send to the device
|
|
returned: always
|
|
type: list
|
|
sample:
|
|
- hostname icx
|
|
- ip domain name test.example.com
|
|
- radius-server host 172.16.10.12 auth-port 2083 acct-port 1850 default key abc dot1x mac-auth
|
|
- tacacs-server host 10.2.3.4 auth-port 4058 authorization-only key xyz
|
|
|
|
"""
|
|
|
|
|
|
import re
|
|
from copy import deepcopy
|
|
from ansible.module_utils.basic import AnsibleModule, env_fallback
|
|
from ansible_collections.community.general.plugins.module_utils.network.icx.icx import get_config, load_config
|
|
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import ComplexList, validate_ip_v6_address
|
|
from ansible.module_utils.connection import Connection, ConnectionError, exec_command
|
|
|
|
|
|
def diff_list(want, have):
|
|
adds = [w for w in want if w not in have]
|
|
removes = [h for h in have if h not in want]
|
|
return (adds, removes)
|
|
|
|
|
|
def map_obj_to_commands(want, have, module):
|
|
commands = list()
|
|
state = module.params['state']
|
|
|
|
def needs_update(x):
|
|
return want.get(x) is not None and (want.get(x) != have.get(x))
|
|
|
|
if state == 'absent':
|
|
if have['name_servers'] == [] and have['aaa_servers'] == [] and have['domain_search'] == [] and have['hostname'] is None:
|
|
if want['hostname']:
|
|
commands.append('no hostname')
|
|
|
|
if want['domain_search']:
|
|
for item in want['domain_search']:
|
|
commands.append('no ip dns domain-list %s' % item)
|
|
|
|
if want['name_servers']:
|
|
for item in want['name_servers']:
|
|
commands.append('no ip dns server-address %s' % item)
|
|
|
|
if want['aaa_servers']:
|
|
want_servers = []
|
|
want_server = want['aaa_servers']
|
|
if want_server:
|
|
want_list = deepcopy(want_server)
|
|
for items in want_list:
|
|
items['auth_key'] = None
|
|
want_servers.append(items)
|
|
for item in want_servers:
|
|
ipv6addr = validate_ip_v6_address(item['hostname'])
|
|
if ipv6addr:
|
|
commands.append('no ' + item['type'] + '-server host ipv6 ' + item['hostname'])
|
|
else:
|
|
commands.append('no ' + item['type'] + '-server host ' + item['hostname'])
|
|
|
|
if want['hostname']:
|
|
if have['hostname'] == want['hostname']:
|
|
commands.append('no hostname')
|
|
|
|
if want['domain_search']:
|
|
for item in want['domain_search']:
|
|
if item in have['domain_search']:
|
|
commands.append('no ip dns domain-list %s' % item)
|
|
|
|
if want['name_servers']:
|
|
for item in want['name_servers']:
|
|
if item in have['name_servers']:
|
|
commands.append('no ip dns server-address %s' % item)
|
|
|
|
if want['aaa_servers']:
|
|
want_servers = []
|
|
want_server = want['aaa_servers']
|
|
have_server = have['aaa_servers']
|
|
if want_server:
|
|
want_list = deepcopy(want_server)
|
|
for items in want_list:
|
|
items['auth_key'] = None
|
|
want_servers.append(items)
|
|
for item in want_servers:
|
|
if item in have_server:
|
|
ipv6addr = validate_ip_v6_address(item['hostname'])
|
|
if ipv6addr:
|
|
commands.append('no ' + item['type'] + '-server host ipv6 ' + item['hostname'])
|
|
else:
|
|
commands.append('no ' + item['type'] + '-server host ' + item['hostname'])
|
|
|
|
elif state == 'present':
|
|
if needs_update('hostname'):
|
|
commands.append('hostname %s' % want['hostname'])
|
|
|
|
if want['domain_search']:
|
|
adds, removes = diff_list(want['domain_search'], have['domain_search'])
|
|
for item in removes:
|
|
commands.append('no ip dns domain-list %s' % item)
|
|
for item in adds:
|
|
commands.append('ip dns domain-list %s' % item)
|
|
|
|
if want['name_servers']:
|
|
adds, removes = diff_list(want['name_servers'], have['name_servers'])
|
|
for item in removes:
|
|
commands.append('no ip dns server-address %s' % item)
|
|
for item in adds:
|
|
commands.append('ip dns server-address %s' % item)
|
|
|
|
if want['aaa_servers']:
|
|
want_servers = []
|
|
want_server = want['aaa_servers']
|
|
have_server = have['aaa_servers']
|
|
want_list = deepcopy(want_server)
|
|
for items in want_list:
|
|
items['auth_key'] = None
|
|
want_servers.append(items)
|
|
|
|
adds, removes = diff_list(want_servers, have_server)
|
|
|
|
for item in removes:
|
|
ip6addr = validate_ip_v6_address(item['hostname'])
|
|
if ip6addr:
|
|
cmd = 'no ' + item['type'] + '-server host ipv6 ' + item['hostname']
|
|
else:
|
|
cmd = 'no ' + item['type'] + '-server host ' + item['hostname']
|
|
commands.append(cmd)
|
|
|
|
for w_item in adds:
|
|
for item in want_server:
|
|
if item['hostname'] == w_item['hostname'] and item['type'] == w_item['type']:
|
|
auth_key = item['auth_key']
|
|
|
|
ip6addr = validate_ip_v6_address(w_item['hostname'])
|
|
if ip6addr:
|
|
cmd = w_item['type'] + '-server host ipv6 ' + w_item['hostname']
|
|
else:
|
|
cmd = w_item['type'] + '-server host ' + w_item['hostname']
|
|
if w_item['auth_port_type']:
|
|
cmd += ' ' + w_item['auth_port_type'] + ' ' + w_item['auth_port_num']
|
|
if w_item['acct_port_num'] and w_item['type'] == 'radius':
|
|
cmd += ' acct-port ' + w_item['acct_port_num']
|
|
if w_item['type'] == 'tacacs':
|
|
if any((w_item['acct_port_num'], w_item['auth_key_type'])):
|
|
module.fail_json(msg='acct_port and auth_key_type is not applicable for tacacs server')
|
|
if w_item['acct_type']:
|
|
cmd += ' ' + w_item['acct_type']
|
|
if auth_key is not None:
|
|
cmd += ' key ' + auth_key
|
|
if w_item['auth_key_type'] and w_item['type'] == 'radius':
|
|
val = ''
|
|
for y in w_item['auth_key_type']:
|
|
val = val + ' ' + y
|
|
cmd += val
|
|
commands.append(cmd)
|
|
|
|
return commands
|
|
|
|
|
|
def parse_hostname(config):
|
|
match = re.search(r'^hostname (\S+)', config, re.M)
|
|
if match:
|
|
return match.group(1)
|
|
|
|
|
|
def parse_domain_search(config):
|
|
match = re.findall(r'^ip dns domain[- ]list (\S+)', config, re.M)
|
|
matches = list()
|
|
for name in match:
|
|
matches.append(name)
|
|
return matches
|
|
|
|
|
|
def parse_name_servers(config):
|
|
matches = list()
|
|
values = list()
|
|
lines = config.split('\n')
|
|
for line in lines:
|
|
if 'ip dns server-address' in line:
|
|
values = line.split(' ')
|
|
for val in values:
|
|
match = re.search(r'([0-9.]+)', val)
|
|
if match:
|
|
matches.append(match.group())
|
|
|
|
return matches
|
|
|
|
|
|
def parse_aaa_servers(config):
|
|
configlines = config.split('\n')
|
|
obj = []
|
|
for line in configlines:
|
|
auth_key_type = []
|
|
if 'radius-server' in line or 'tacacs-server' in line:
|
|
aaa_type = 'radius' if 'radius-server' in line else 'tacacs'
|
|
match = re.search(r'(host ipv6 (\S+))|(host (\S+))', line)
|
|
if match:
|
|
hostname = match.group(2) if match.group(2) is not None else match.group(4)
|
|
match = re.search(r'auth-port ([0-9]+)', line)
|
|
if match:
|
|
auth_port_num = match.group(1)
|
|
else:
|
|
auth_port_num = None
|
|
match = re.search(r'acct-port ([0-9]+)', line)
|
|
if match:
|
|
acct_port_num = match.group(1)
|
|
else:
|
|
acct_port_num = None
|
|
match = re.search(r'acct-port [0-9]+ (\S+)', line)
|
|
if match:
|
|
acct_type = match.group(1)
|
|
else:
|
|
acct_type = None
|
|
if aaa_type == 'tacacs':
|
|
match = re.search(r'auth-port [0-9]+ (\S+)', line)
|
|
if match:
|
|
acct_type = match.group(1)
|
|
else:
|
|
acct_type = None
|
|
match = re.search(r'(dot1x)', line)
|
|
if match:
|
|
auth_key_type.append('dot1x')
|
|
match = re.search(r'(mac-auth)', line)
|
|
if match:
|
|
auth_key_type.append('mac-auth')
|
|
match = re.search(r'(web-auth)', line)
|
|
if match:
|
|
auth_key_type.append('web-auth')
|
|
|
|
obj.append({
|
|
'type': aaa_type,
|
|
'hostname': hostname,
|
|
'auth_port_type': 'auth-port',
|
|
'auth_port_num': auth_port_num,
|
|
'acct_port_num': acct_port_num,
|
|
'acct_type': acct_type,
|
|
'auth_key': None,
|
|
'auth_key_type': set(auth_key_type) if len(auth_key_type) > 0 else None
|
|
})
|
|
|
|
return obj
|
|
|
|
|
|
def map_config_to_obj(module):
|
|
compare = module.params['check_running_config']
|
|
config = get_config(module, None, compare=compare)
|
|
return {
|
|
'hostname': parse_hostname(config),
|
|
'domain_search': parse_domain_search(config),
|
|
'name_servers': parse_name_servers(config),
|
|
'aaa_servers': parse_aaa_servers(config)
|
|
}
|
|
|
|
|
|
def map_params_to_obj(module):
|
|
if module.params['aaa_servers']:
|
|
for item in module.params['aaa_servers']:
|
|
if item['auth_key_type']:
|
|
item['auth_key_type'] = set(item['auth_key_type'])
|
|
obj = {
|
|
'hostname': module.params['hostname'],
|
|
'domain_name': module.params['domain_name'],
|
|
'domain_search': module.params['domain_search'],
|
|
'name_servers': module.params['name_servers'],
|
|
'state': module.params['state'],
|
|
'aaa_servers': module.params['aaa_servers']
|
|
}
|
|
return obj
|
|
|
|
|
|
def main():
|
|
""" Main entry point for Ansible module execution
|
|
"""
|
|
server_spec = dict(
|
|
type=dict(choices=['radius', 'tacacs']),
|
|
hostname=dict(),
|
|
auth_port_type=dict(choices=['auth-port']),
|
|
auth_port_num=dict(),
|
|
acct_port_num=dict(),
|
|
acct_type=dict(choices=['accounting-only', 'authentication-only', 'authorization-only', 'default']),
|
|
auth_key=dict(),
|
|
auth_key_type=dict(type='list', choices=['dot1x', 'mac-auth', 'web-auth'])
|
|
)
|
|
argument_spec = dict(
|
|
hostname=dict(),
|
|
|
|
domain_name=dict(type='list'),
|
|
domain_search=dict(type='list'),
|
|
name_servers=dict(type='list'),
|
|
|
|
aaa_servers=dict(type='list', elements='dict', options=server_spec),
|
|
state=dict(choices=['present', 'absent'], default='present'),
|
|
check_running_config=dict(default=True, type='bool', fallback=(env_fallback, ['ANSIBLE_CHECK_ICX_RUNNING_CONFIG']))
|
|
)
|
|
|
|
module = AnsibleModule(argument_spec=argument_spec,
|
|
supports_check_mode=True)
|
|
|
|
result = {'changed': False}
|
|
|
|
warnings = list()
|
|
|
|
result['warnings'] = warnings
|
|
exec_command(module, 'skip')
|
|
want = map_params_to_obj(module)
|
|
have = map_config_to_obj(module)
|
|
commands = map_obj_to_commands(want, have, module)
|
|
result['commands'] = commands
|
|
|
|
if commands:
|
|
if not module.check_mode:
|
|
load_config(module, commands)
|
|
result['changed'] = True
|
|
|
|
module.exit_json(**result)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|