Initial commit

This commit is contained in:
Ansible Core Team
2020-03-09 09:11:07 +00:00
commit aebc1b03fd
4861 changed files with 812621 additions and 0 deletions

View File

@@ -0,0 +1,215 @@
#!/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_banner
author: "Ruckus Wireless (@Commscope)"
short_description: Manage multiline banners on Ruckus ICX 7000 series switches
description:
- This will configure both login and motd banners on remote
ruckus ICX 7000 series switches. It allows playbooks to add or remove
banner text from the active running configuration.
notes:
- Tested against ICX 10.1
options:
banner:
description:
- Specifies which banner should be configured on the remote device.
type: str
required: true
choices: ['motd', 'exec', 'incoming']
text:
description:
- The banner text that should be
present in the remote device running configuration.
This argument accepts a multiline string, with no empty lines.
type: str
state:
description:
- Specifies whether or not the configuration is
present in the current devices active running configuration.
type: str
default: present
choices: ['present', 'absent']
enterkey:
description:
- Specifies whether or not the motd configuration should accept
the require-enter-key
type: bool
default: no
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 the motd banner
icx_banner:
banner: motd
text: |
this is my motd banner
that contains a multiline
string
state: present
- name: remove the motd banner
icx_banner:
banner: motd
state: absent
- name: configure require-enter-key for motd
icx_banner:
banner: motd
enterkey: True
- name: remove require-enter-key for motd
icx_banner:
banner: motd
enterkey: False
"""
RETURN = """
commands:
description: The list of configuration mode commands to send to the device
returned: always
type: list
sample:
- banner motd
- this is my motd banner
- that contains a multiline
- string
"""
import re
from ansible.module_utils._text import to_text
from ansible.module_utils.connection import exec_command
from ansible.module_utils.basic import AnsibleModule, env_fallback
from ansible_collections.community.general.plugins.module_utils.network.icx.icx import load_config, get_config
from ansible.module_utils.connection import Connection, ConnectionError
def map_obj_to_commands(updates, module):
commands = list()
state = module.params['state']
want, have = updates
if module.params['banner'] != 'motd' and module.params['enterkey']:
module.fail_json(msg=module.params['banner'] + " banner can have text only, got enterkey")
if state == 'absent':
if 'text' in have.keys() and have['text']:
commands.append('no banner %s' % module.params['banner'])
if(module.params['enterkey'] is False):
commands.append('no banner %s require-enter-key' % module.params['banner'])
elif state == 'present':
if module.params['text'] is None and module.params['enterkey'] is None:
module.fail_json(msg=module.params['banner'] + " one of the following is required: text, enterkey:only if motd")
if module.params["banner"] == "motd" and want['enterkey'] != have['enterkey']:
if(module.params['enterkey']):
commands.append('banner %s require-enter-key' % module.params['banner'])
if want['text'] and (want['text'] != have.get('text')):
module.params["enterkey"] = None
banner_cmd = 'banner %s' % module.params['banner']
banner_cmd += ' $\n'
banner_cmd += module.params['text'].strip()
banner_cmd += '\n$'
commands.append(banner_cmd)
return commands
def map_config_to_obj(module):
compare = module.params.get('check_running_config')
obj = {'banner': module.params['banner'], 'state': 'absent', 'enterkey': False}
exec_command(module, 'skip')
output_text = ''
output_re = ''
out = get_config(module, flags=['| begin banner %s'
% module.params['banner']], compare=module.params['check_running_config'])
if out:
try:
output_re = re.search(r'banner %s( require-enter-key)' % module.params['banner'], out, re.S).group(0)
obj['enterkey'] = True
except BaseException:
pass
try:
output_text = re.search(r'banner %s (\$([^\$])+\$){1}' % module.params['banner'], out, re.S).group(1).strip('$\n')
except BaseException:
pass
else:
output_text = None
if output_text:
obj['text'] = output_text
obj['state'] = 'present'
if module.params['check_running_config'] is False:
obj = {'banner': module.params['banner'], 'state': 'absent', 'enterkey': False, 'text': 'JUNK'}
return obj
def map_params_to_obj(module):
text = module.params['text']
if text:
text = str(text).strip()
return {
'banner': module.params['banner'],
'text': text,
'state': module.params['state'],
'enterkey': module.params['enterkey']
}
def main():
"""entry point for module execution
"""
argument_spec = dict(
banner=dict(required=True, choices=['motd', 'exec', 'incoming']),
text=dict(),
enterkey=dict(type='bool'),
state=dict(default='present', choices=['present', 'absent']),
check_running_config=dict(default=True, type='bool', fallback=(env_fallback, ['ANSIBLE_CHECK_ICX_RUNNING_CONFIG']))
)
required_one_of = [['text', 'enterkey', 'state']]
module = AnsibleModule(argument_spec=argument_spec,
required_one_of=required_one_of,
supports_check_mode=True)
warnings = list()
results = {'changed': False}
want = map_params_to_obj(module)
have = map_config_to_obj(module)
commands = map_obj_to_commands((want, have), module)
results['commands'] = commands
if commands:
if not module.check_mode:
response = load_config(module, commands)
results['changed'] = True
module.exit_json(**results)
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,232 @@
#!/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_command
author: "Ruckus Wireless (@Commscope)"
short_description: Run arbitrary commands on remote Ruckus ICX 7000 series switches
description:
- Sends arbitrary commands to an ICX node and returns the results
read from the device. This module includes an
argument that will cause the module to wait for a specific condition
before returning or timing out if the condition is not met.
notes:
- Tested against ICX 10.1
options:
commands:
description:
- List of commands to send to the remote ICX device over the
configured provider. The resulting output from the command
is returned. If the I(wait_for) argument is provided, the
module is not returned until the condition is satisfied or
the number of retries has expired. If a command sent to the
device requires answering a prompt, checkall and newline if
multiple prompts, it is possible to pass
a dict containing I(command), I(answer), I(prompt), I(check_all)
and I(newline).Common answers are 'y' or "\r" (carriage return,
must be double quotes). See examples.
type: list
required: true
wait_for:
description:
- List of conditions to evaluate against the output of the
command. The task will wait for each condition to be true
before moving forward. If the conditional is not true
within the configured number of retries, the task fails.
See examples.
type: list
aliases: ['waitfor']
match:
description:
- The I(match) argument is used in conjunction with the
I(wait_for) argument to specify the match policy. Valid
values are C(all) or C(any). If the value is set to C(all)
then all conditionals in the wait_for must be satisfied. If
the value is set to C(any) then only one of the values must be
satisfied.
type: str
default: all
choices: ['any', 'all']
retries:
description:
- Specifies the number of times a command should by tried
before it is considered failed. The command is run on the
target device every retry and evaluated against the
I(wait_for) conditions.
type: int
default: 10
interval:
description:
- Configures the interval in seconds to wait between retries
of the command. If the command does not pass the specified
conditions, the interval indicates how long to wait before
trying the command again.
type: int
default: 1
'''
EXAMPLES = """
tasks:
- name: run show version on remote devices
icx_command:
commands: show version
- name: run show version and check to see if output contains ICX
icx_command:
commands: show version
wait_for: result[0] contains ICX
- name: run multiple commands on remote nodes
icx_command:
commands:
- show version
- show interfaces
- name: run multiple commands and evaluate the output
icx_command:
commands:
- show version
- show interfaces
wait_for:
- result[0] contains ICX
- result[1] contains GigabitEthernet1/1/1
- name: run commands that require answering a prompt
icx_command:
commands:
- command: 'service password-encryption sha1'
prompt: 'Warning: Moving to higher password-encryption type,.*'
answer: 'y'
- name: run commands that require answering multiple prompt
icx_command:
commands:
- command: 'username qqq password qqq'
prompt:
- 'User already exists. Do you want to modify:.*'
- 'To modify or remove user, enter current password:'
answer:
- 'y'
- 'qqq\\\r'
check_all: True
newline: False
"""
RETURN = """
stdout:
description: The set of responses from the commands
returned: always apart from low level errors
type: list
sample: ['...', '...']
stdout_lines:
description: The value of stdout split into a list
returned: always apart from low level errors
type: list
sample: [['...', '...'], ['...'], ['...']]
failed_conditions:
description: The list of conditionals that have failed
returned: failed
type: list
sample: ['...', '...']
"""
import re
import time
from ansible_collections.community.general.plugins.module_utils.network.icx.icx import run_commands
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import ComplexList, to_lines
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.parsing import Conditional
from ansible.module_utils.six import string_types
def parse_commands(module, warnings):
command = ComplexList(dict(
command=dict(key=True),
prompt=dict(),
answer=dict(),
check_all=dict(type='bool', default='False'),
newline=dict(type='bool', default='True')
), module)
commands = command(module.params['commands'])
for item in list(commands):
if module.check_mode:
if not item['command'].startswith('show'):
warnings.append(
'Only show commands are supported when using check mode, not executing configure terminal')
commands.remove(item)
return commands
def main():
"""main entry point for module execution
"""
argument_spec = dict(
commands=dict(type='list', required=True),
wait_for=dict(type='list', aliases=['waitfor']),
match=dict(default='all', choices=['all', 'any']),
retries=dict(default=10, type='int'),
interval=dict(default=1, type='int')
)
module = AnsibleModule(argument_spec=argument_spec,
supports_check_mode=True)
result = {'changed': False}
warnings = list()
run_commands(module, ['skip'])
commands = parse_commands(module, warnings)
result['warnings'] = warnings
wait_for = module.params['wait_for'] or list()
conditionals = [Conditional(c) for c in wait_for]
retries = module.params['retries']
interval = module.params['interval']
match = module.params['match']
while retries > 0:
responses = run_commands(module, commands)
for item in list(conditionals):
if item(responses):
if match == 'any':
conditionals = list()
break
conditionals.remove(item)
if not conditionals:
break
time.sleep(interval)
retries -= 1
if conditionals:
failed_conditions = [item.raw for item in conditionals]
msg = 'One or more conditional statements have not been satisfied'
module.fail_json(msg=msg, failed_conditions=failed_conditions)
result.update({
'changed': False,
'stdout': responses,
'stdout_lines': list(to_lines(responses))
})
module.exit_json(**result)
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,483 @@
#!/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_config
author: "Ruckus Wireless (@Commscope)"
short_description: Manage configuration sections of Ruckus ICX 7000 series switches
description:
- Ruckus ICX configurations use a simple block indent file syntax
for segmenting configuration into sections. This module provides
an implementation for working with ICX configuration sections in
a deterministic way.
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:
lines:
description:
- The ordered set of commands that should be configured in the
section. The commands must be the exact same commands as found
in the device running-config. Be sure to note the configuration
command syntax as some commands are automatically modified by the
device config parser.
type: list
aliases: ['commands']
parents:
description:
- The ordered set of parents that uniquely identify the section or hierarchy
the commands should be checked against. If the parents argument
is omitted, the commands are checked against the set of top
level or global commands.
type: list
src:
description:
- Specifies the source path to the file that contains the configuration
or configuration template to load. The path to the source file can
either be the full path on the Ansible control host or a relative
path from the playbook or role root directory. This argument is mutually
exclusive with I(lines), I(parents).
type: str
before:
description:
- The ordered set of commands to push on to the command stack if
a change needs to be made. This allows the playbook designer
the opportunity to perform configuration commands prior to pushing
any changes without affecting how the set of commands are matched
against the system.
type: list
after:
description:
- The ordered set of commands to append to the end of the command
stack if a change needs to be made. Just like with I(before) this
allows the playbook designer to append a set of commands to be
executed after the command set.
type: list
match:
description:
- Instructs the module on the way to perform the matching of
the set of commands against the current device config. If
match is set to I(line), commands are matched line by line. If
match is set to I(strict), command lines are matched with respect
to position. If match is set to I(exact), command lines
must be an equal match. Finally, if match is set to I(none), the
module will not attempt to compare the source configuration with
the running configuration on the remote device.
type: str
choices: ['line', 'strict', 'exact', 'none']
default: line
replace:
description:
- Instructs the module on the way to perform the configuration
on the device. If the replace argument is set to I(line) then
the modified lines are pushed to the device in configuration
mode. If the replace argument is set to I(block) then the entire
command block is pushed to the device in configuration mode if any
line is not correct.
type: str
default: line
choices: ['line', 'block']
multiline_delimiter:
description:
- This argument is used when pushing a multiline configuration
element to the ICX device. It specifies the character to use
as the delimiting character. This only applies to the
configuration action.
type: str
default: "@"
backup:
description:
- This argument will cause the module to create a full backup of
the current C(running-config) from the remote device before any
changes are made. The backup file is written to the C(backup)
folder in the playbook root directory or role root directory, if
playbook is part of an ansible role. If the directory does not exist,
it is created.
type: bool
default: 'no'
defaults:
description:
- This argument specifies whether or not to collect all defaults
when getting the remote device running config. When enabled,
the module will get the current config by issuing the command
C(show running-config all).
type: bool
default: 'no'
running_config:
description:
- The module, by default, will connect to the remote device and
retrieve the current running-config to use as a base for comparing
against the contents of source. There are times when it is not
desirable to have the task get the current running-config for
every task in a playbook. The I(running_config) argument allows the
implementer to pass in the configuration to use as the base
config for comparison.
type: str
aliases: ['config']
save_when:
description:
- When changes are made to the device running-configuration, the
changes are not copied to non-volatile storage by default. Using
this argument will change that before. If the argument is set to
I(always), then the running-config will always be copied to the
start-up configuration and the I(modified) flag will always be set to
True. If the argument is set to I(modified), then the running-config
will only be copied to the start-up configuration if it has changed since
the last save to configuration. If the argument is set to
I(never), the running-config will never be copied to the
configuration. If the argument is set to I(changed), then the running-config
will only be copied to the configuration if the task has made a change.
type: str
default: never
choices: ['always', 'never', 'modified', 'changed']
diff_against:
description:
- When using the C(ansible-playbook --diff) command line argument
the module can generate diffs against different sources.
- When this option is configure as I(startup), the module will return
the diff of the running-config against the configuration.
- When this option is configured as I(intended), the module will
return the diff of the running-config against the configuration
provided in the C(intended_config) argument.
- When this option is configured as I(running), the module will
return the before and after diff of the running-config with respect
to any changes made to the device configuration.
type: str
choices: ['running', 'startup', 'intended']
diff_ignore_lines:
description:
- Use this argument to specify one or more lines that should be
ignored during the diff. This is used for lines in the configuration
that are automatically updated by the system. This argument takes
a list of regular expressions or exact line matches.
type: list
intended_config:
description:
- The C(intended_config) provides the master configuration that
the node should conform to and is used to check the final
running-config against. This argument will not modify any settings
on the remote device and is strictly used to check the compliance
of the current device's configuration against. When specifying this
argument, the task should also modify the C(diff_against) value and
set it to I(intended).
type: str
'''
EXAMPLES = """
- name: configure top level configuration
icx_config:
lines: hostname {{ inventory_hostname }}
- name: configure interface settings
icx_config:
lines:
- port-name test string
- ip address 172.31.1.1 255.255.255.0
parents: interface ethernet 1/1/2
- name: configure ip helpers on multiple interfaces
icx_config:
lines:
- ip helper-address 172.26.1.10
- ip helper-address 172.26.3.8
parents: "{{ item }}"
with_items:
- interface ethernet 1/1/2
- interface ethernet 1/1/3
- name: load new acl into device
icx_config:
lines:
- permit ip host 192.0.2.1 any log
- permit ip host 192.0.2.2 any log
- permit ip host 192.0.2.3 any log
- permit ip host 192.0.2.4 any log
parents: ip access-list extended test
before: no ip access-list extended test
match: exact
- name: check the running-config against master config
icx_config:
diff_against: intended
intended_config: "{{ lookup('file', 'master.cfg') }}"
- name: check the configuration against the running-config
icx_config:
diff_against: startup
diff_ignore_lines:
- ntp clock .*
- name: for idempotency, use full-form commands
icx_config:
lines:
# - en
- enable
# parents: int eth1/0/11
parents: interface ethernet 1/1/2
# Set boot image based on comparison to a group_var (version) and the version
# that is returned from the `icx_facts` module
- name: SETTING BOOT IMAGE
icx_config:
lines:
- no boot system
- boot system flash bootflash:{{new_image}}
host: "{{ inventory_hostname }}"
when: ansible_net_version != version
- name: render template onto an ICX device
icx_config:
backup: yes
src: "{{ lookup('file', 'config.j2') }}"
"""
RETURN = """
updates:
description: The set of commands that will be pushed to the remote device
returned: always
type: list
sample: ['hostname foo', 'router ospf 1', 'router-id 192.0.2.1']
commands:
description: The set of commands that will be pushed to the remote device
returned: always
type: list
sample: ['hostname foo', 'router ospf 1', 'router-id 192.0.2.1']
backup_path:
description: The full path to the backup file
returned: when backup is yes
type: str
sample: /playbooks/ansible/backup/icx_config.2016-07-16@22:28:34
"""
import json
from ansible.module_utils._text import to_text
from ansible.module_utils.connection import ConnectionError
from ansible_collections.community.general.plugins.module_utils.network.icx.icx import run_commands, get_config
from ansible_collections.community.general.plugins.module_utils.network.icx.icx import get_defaults_flag, get_connection
from ansible_collections.community.general.plugins.module_utils.network.icx.icx import check_args as icx_check_args
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.config import NetworkConfig, dumps
def check_args(module, warnings):
icx_check_args(module, warnings)
if module.params['multiline_delimiter']:
if len(module.params['multiline_delimiter']) != 1:
module.fail_json(msg='multiline_delimiter value can only be a '
'single character')
def edit_config_or_macro(connection, commands):
if "macro name" in commands[0]:
connection.edit_macro(candidate=commands)
else:
if commands[0] != '':
connection.edit_config(candidate=commands)
def get_candidate_config(module):
candidate = ''
if module.params['src']:
candidate = module.params['src']
elif module.params['lines']:
candidate_obj = NetworkConfig(indent=1)
parents = module.params['parents'] or list()
candidate_obj.add(module.params['lines'], parents=parents)
candidate = dumps(candidate_obj, 'raw')
return candidate
def get_running_config(module, current_config=None, flags=None):
running = module.params['running_config']
if not running:
if not module.params['defaults'] and current_config:
running = current_config
else:
running = get_config(module, flags=flags)
return running
def save_config(module, result):
result['changed'] = True
if not module.check_mode:
run_commands(module, 'write memory')
else:
module.warn('Skipping command `copy running-config start-up configuration` '
'due to check_mode. Configuration not copied to '
'non-volatile storage')
def main():
""" main entry point for module execution
"""
argument_spec = dict(
src=dict(),
lines=dict(aliases=['commands'], type='list'),
parents=dict(type='list'),
before=dict(type='list'),
after=dict(type='list'),
match=dict(default='line', choices=['line', 'strict', 'exact', 'none']),
replace=dict(default='line', choices=['line', 'block']),
multiline_delimiter=dict(default='@'),
running_config=dict(aliases=['config']),
intended_config=dict(),
defaults=dict(type='bool', default=False),
backup=dict(type='bool', default=False),
save_when=dict(choices=['always', 'never', 'modified', 'changed'], default='never'),
diff_against=dict(choices=['startup', 'intended', 'running']),
diff_ignore_lines=dict(type='list'),
)
mutually_exclusive = [('lines', 'src'),
('parents', 'src')]
required_if = [('match', 'strict', ['lines']),
('match', 'exact', ['lines']),
('replace', 'block', ['lines']),
('diff_against', 'intended', ['intended_config'])]
module = AnsibleModule(argument_spec=argument_spec,
mutually_exclusive=mutually_exclusive,
required_if=required_if,
supports_check_mode=True)
result = {'changed': False}
warnings = list()
check_args(module, warnings)
result['warnings'] = warnings
run_commands(module, 'skip')
diff_ignore_lines = module.params['diff_ignore_lines']
config = None
contents = None
flags = None if module.params['defaults'] else []
connection = get_connection(module)
if module.params['backup'] or (module._diff and module.params['diff_against'] == 'running'):
contents = get_config(module, flags=flags)
config = NetworkConfig(indent=1, contents=contents)
if module.params['backup']:
result['__backup__'] = contents
if any((module.params['lines'], module.params['src'])):
match = module.params['match']
replace = module.params['replace']
path = module.params['parents']
candidate = get_candidate_config(module)
running = get_running_config(module, contents, flags=flags)
try:
response = connection.get_diff(candidate=candidate, running=running, diff_match=match, diff_ignore_lines=diff_ignore_lines, path=path,
diff_replace=replace)
except ConnectionError as exc:
module.fail_json(msg=to_text(exc, errors='surrogate_then_replace'))
config_diff = response['config_diff']
banner_diff = response['banner_diff']
if config_diff or banner_diff:
commands = config_diff.split('\n')
if module.params['before']:
commands[:0] = module.params['before']
if module.params['after']:
commands.extend(module.params['after'])
result['commands'] = commands
result['updates'] = commands
result['banners'] = banner_diff
# send the configuration commands to the device and merge
# them with the current running config
if not module.check_mode:
if commands:
edit_config_or_macro(connection, commands)
if banner_diff:
connection.edit_banner(candidate=json.dumps(banner_diff), multiline_delimiter=module.params['multiline_delimiter'])
result['changed'] = True
running_config = module.params['running_config']
startup_config = None
if module.params['save_when'] == 'always':
save_config(module, result)
elif module.params['save_when'] == 'modified':
output = run_commands(module, ['show running-config', 'show configuration'])
running_config = NetworkConfig(indent=1, contents=output[0], ignore_lines=diff_ignore_lines)
startup_config = NetworkConfig(indent=1, contents=output[1], ignore_lines=diff_ignore_lines)
if running_config.sha1 != startup_config.sha1:
save_config(module, result)
elif module.params['save_when'] == 'changed' and result['changed']:
save_config(module, result)
if module._diff:
if not running_config:
output = run_commands(module, 'show running-config')
contents = output[0]
else:
contents = running_config
# recreate the object in order to process diff_ignore_lines
running_config = NetworkConfig(indent=1, contents=contents, ignore_lines=diff_ignore_lines)
if module.params['diff_against'] == 'running':
if module.check_mode:
module.warn("unable to perform diff against running-config due to check mode")
contents = None
else:
contents = config.config_text
elif module.params['diff_against'] == 'startup':
if not startup_config:
output = run_commands(module, 'show configuration')
contents = output[0]
else:
contents = startup_config.config_text
elif module.params['diff_against'] == 'intended':
contents = module.params['intended_config']
if contents is not None:
base_config = NetworkConfig(indent=1, contents=contents, ignore_lines=diff_ignore_lines)
if running_config.sha1 != base_config.sha1:
if module.params['diff_against'] == 'intended':
before = running_config
after = base_config
elif module.params['diff_against'] in ('startup', 'running'):
before = base_config
after = running_config
result.update({
'changed': True,
'diff': {'before': str(before), 'after': str(after)}
})
module.exit_json(**result)
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,372 @@
#!/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_copy
author: "Ruckus Wireless (@Commscope)"
short_description: Transfer files from or to remote Ruckus ICX 7000 series switches
description:
- This module transfers files from or to remote devices running ICX.
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:
upload:
description:
- Name of the resource to be uploaded. Mutually exclusive with download.
type: str
choices: ['running-config', 'startup-config', 'flash_primary', 'flash_secondary']
download:
description:
- Name of the resource to be downloaded. Mutually exclusive with upload.
type: str
choices: ['running-config', 'startup-config', 'flash_primary', 'flash_secondary', 'bootrom', 'fips-primary-sig', 'fips-secondary-sig', 'fips-bootrom-sig']
protocol:
description:
- Data transfer protocol to be used
type: str
choices: ['scp', 'https']
required: true
remote_server:
description:
- IP address of the remote server
type: str
required: true
remote_port:
description:
- The port number of the remote host. Default values will be selected based on protocol type.
Default scp:22, http:443
type: str
remote_filename:
description:
- The name or path of the remote file/resource to be uploaded or downloaded.
type: str
required: true
remote_user:
description:
- remote username to be used for scp login.
type: str
remote_pass:
description:
- remote password to be used for scp login.
type: str
public_key:
description:
- public key type to be used to login to scp server
type: str
choices: ['rsa', 'dsa']
'''
EXAMPLES = """
- name: upload running-config to the remote scp server
icx_copy:
upload: running-config
protocol: scp
remote_server: 172.16.10.49
remote_filename: running.conf
remote_user: user1
remote_pass: pass123
- name: download running-config from the remote scp server
icx_copy:
download: running-config
protocol: scp
remote_server: 172.16.10.49
remote_filename: running.conf
remote_user: user1
remote_pass: pass123
- name: download running-config from the remote scp server using rsa public key
icx_copy:
download: running-config
protocol: scp
remote_server: 172.16.10.49
remote_filename: running.conf
remote_user: user1
remote_pass: pass123
public_key: rsa
- name: upload startup-config to the remote https server
icx_copy:
upload: startup-config
protocol: https
remote_server: 172.16.10.49
remote_filename: config/running.conf
remote_user: user1
remote_pass: pass123
- name: upload startup-config to the remote https server
icx_copy:
upload: startup-config
protocol: https
remote_server: 172.16.10.49
remote_filename: config/running.conf
remote_user: user1
remote_pass: pass123
- name: Download OS image into the flash from remote scp ipv6 server
icx_copy:
download: startup-config
protocol: scp
remote_server: ipv6 FE80:CD00:0000:0CDE:1257:0000:211E:729C
remote_filename: img.bin
remote_user: user1
remote_pass: pass123
- name: Download OS image into the secondary flash from remote scp ipv6 server
icx_copy:
Download: flash_secondary
protocol: scp
remote_server: ipv6 FE80:CD00:0000:0CDE:1257:0000:211E:729C
remote_filename: img.bin
remote_user: user1
remote_pass: pass123
- name: Download OS image into the secondary flash from remote scp ipv6 server on port 5000
icx_copy:
Download: flash_secondary
protocol: scp
remote_server: ipv6 FE80:CD00:0000:0CDE:1257:0000:211E:729C
remote_port: 5000
remote_filename: img.bin
remote_user: user1
remote_pass: pass123
- name: Download OS image into the primary flash from remote https ipv6 server
icx_copy:
Download: flash_primary
protocol: https
remote_server: ipv6 FE80:CD00:0000:0CDE:1257:0000:211E:729C
remote_filename: images/img.bin
remote_user: user1
remote_pass: pass123
- name: Download OS image into the primary flash from remote https ipv6 server on port 8080
icx_copy:
Download: flash_primary
protocol: https
remote_server: ipv6 FE80:CD00:0000:0CDE:1257:0000:211E:729C
remote_port: 8080
remote_filename: images/img.bin
remote_user: user1
remote_pass: pass123
"""
RETURN = """
changed:
description: true when downloaded any configuration or flash. false otherwise.
returned: always
type: bool
"""
from ansible.module_utils._text import to_text
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.connection import ConnectionError, exec_command
from ansible_collections.community.general.plugins.module_utils.network.icx.icx import exec_scp, run_commands
def map_params_to_obj(module):
command = dict()
if(module.params['protocol'] == 'scp'):
if(module.params['upload'] is not None):
module.params["upload"] = module.params["upload"].replace("flash_primary", "primary")
module.params["upload"] = module.params["upload"].replace("flash_secondary", "secondary")
if(module.params["upload"] == 'running-config' or module.params["upload"] == 'startup-config'):
command["command"] = "copy %s scp %s%s %s%s" % (module.params['upload'],
module.params["remote_server"],
" " + module.params["remote_port"] if module.params["remote_port"] else "",
module.params["remote_filename"],
"public-key " + module.params["public_key"] if module.params["public_key"] else "")
else:
command["command"] = "copy flash scp %s%s %s%s %s" % (module.params["remote_server"],
" " + module.params["remote_port"] if module.params["remote_port"] else "",
module.params["remote_filename"],
"public-key " + module.params["public_key"] if module.params["public_key"] else "",
module.params["upload"])
command["scp_user"] = module.params["remote_user"]
command["scp_pass"] = module.params["remote_pass"]
if(module.params['download'] is not None):
module.params["download"] = module.params["download"].replace("flash_primary", "primary")
module.params["download"] = module.params["download"].replace("flash_secondary", "secondary")
if(module.params["download"] == 'running-config' or module.params["download"] == 'startup-config'):
command["command"] = "copy scp %s %s%s %s%s" % (module.params['download'],
module.params["remote_server"],
" " + module.params["remote_port"] if module.params["remote_port"] else "",
module.params["remote_filename"],
"public-key " + module.params["public_key"] if module.params["public_key"] else "")
else:
command["command"] = "copy scp flash %s%s %s%s %s" % (module.params["remote_server"],
" " + module.params["remote_port"] if module.params["remote_port"] else "",
module.params["remote_filename"],
"public-key " + module.params["public_key"] if module.params["public_key"] else "",
module.params["download"])
command["scp_user"] = module.params["remote_user"]
command["scp_pass"] = module.params["remote_pass"]
if(module.params['protocol'] == 'https'):
if(module.params['upload'] is not None):
module.params["upload"] = module.params["upload"].replace("flash_primary", "primary")
module.params["upload"] = module.params["upload"].replace("flash_secondary", "secondary")
if(module.params["upload"] == 'running-config' or module.params["upload"] == 'startup-config'):
command["command"] = "copy %s https %s %s%s" % (module.params['upload'],
module.params["remote_server"],
module.params["remote_filename"],
" port " + module.params["remote_port"] if module.params["remote_port"] else "")
else:
command["command"] = "copy https flash %s %s %s%s" % (module.params["remote_server"],
module.params["remote_filename"],
module.params['upload'],
" port " + module.params["remote_port"] if module.params["remote_port"] else "")
if(module.params['download'] is not None):
module.params["download"] = module.params["download"].replace("flash_primary", "primary")
module.params["download"] = module.params["download"].replace("flash_secondary", "secondary")
if(module.params["download"] == 'running-config' or module.params["download"] == 'startup-config'):
command["command"] = "copy https %s %s %s%s" % (module.params['download'],
module.params["remote_server"],
module.params["remote_filename"],
" port " + module.params["remote_port"] if module.params["remote_port"] else "")
else:
command["command"] = "copy https flash %s %s %s%s" % (module.params["remote_server"],
module.params["remote_filename"],
module.params['download'],
" port " + module.params["remote_port"] if module.params["remote_port"] else "")
return command
def checkValidations(module):
validation = dict(
scp=dict(
upload=[
'running-config',
'startup-config',
'flash_primary',
'flash_secondary'],
download=[
'running-config',
'startup-config',
'flash_primary',
'flash_secondary',
'bootrom',
'fips-primary-sig',
'fips-secondary-sig',
'fips-bootrom-sig']),
https=dict(
upload=[
'running-config',
'startup-config'],
download=[
'flash_primary',
'flash_secondary',
'startup-config']))
protocol = module.params['protocol']
upload = module.params['upload']
download = module.params['download']
if(protocol == 'scp' and module.params['remote_user'] is None):
module.fail_json(msg="While using scp remote_user argument is required")
if(upload is None and download is None):
module.fail_json(msg="Upload or download params are required.")
if(upload is not None and download is not None):
module.fail_json(msg="Only upload or download can be used at a time.")
if(upload):
if(not (upload in validation.get(protocol).get("upload"))):
module.fail_json(msg="Specified resource '" + upload + "' can't be uploaded to '" + protocol + "'")
if(download):
if(not (download in validation.get(protocol).get("download"))):
module.fail_json(msg="Specified resource '" + download + "' can't be downloaded from '" + protocol + "'")
def main():
"""entry point for module execution
"""
argument_spec = dict(
upload=dict(
type='str',
required=False,
choices=[
'running-config',
'flash_primary',
'flash_secondary',
'startup-config']),
download=dict(
type='str',
required=False,
choices=[
'running-config',
'startup-config',
'flash_primary',
'flash_secondary',
'bootrom',
'fips-primary-sig',
'fips-secondary-sig',
'fips-bootrom-sig']),
protocol=dict(
type='str',
required=True,
choices=[
'https',
'scp']),
remote_server=dict(
type='str',
required=True),
remote_port=dict(
type='str',
required=False),
remote_filename=dict(
type='str',
required=True),
remote_user=dict(
type='str',
required=False),
remote_pass=dict(
type='str',
required=False,
no_log=True),
public_key=dict(
type='str',
required=False,
choices=[
'rsa',
'dsa']))
mutually_exclusive = [['upload', 'download']]
module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False, mutually_exclusive=mutually_exclusive)
checkValidations(module)
warnings = list()
result = {'changed': False, 'warnings': warnings}
exec_command(module, 'skip')
response = ''
try:
command = map_params_to_obj(module)
result['commands'] = [command["command"]]
if(module.params['protocol'] == 'scp'):
response = exec_scp(module, command)
else:
response = run_commands(module, command)
if('Response Code: 404' in response):
module.fail_json(msg=response)
else:
result['response'] = "in progress..."
if(module.params["download"] is not None):
result['changed'] = True
except ConnectionError as exc:
module.fail_json(msg=to_text(exc, errors='surrogate_then_replace'))
module.exit_json(**result)
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,548 @@
#!/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_facts
author: "Ruckus Wireless (@Commscope)"
short_description: Collect facts from remote Ruckus ICX 7000 series switches
description:
- Collects a base set of device facts from a remote device that
is running ICX. This module prepends all of the
base network fact keys with C(ansible_net_<fact>). The facts
module will always collect a base set of facts from the device
and can enable or disable collection of additional facts.
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:
gather_subset:
description:
- When supplied, this argument will restrict the facts collected
to a given subset. Possible values for this argument include
all, hardware, config, and interfaces. Can specify a list of
values to include a larger subset. Values can also be used
with an initial C(M(!)) to specify that a specific subset should
not be collected.
required: false
type: list
default: '!config'
'''
EXAMPLES = """
# Collect all facts from the device
- icx_facts:
gather_subset: all
# Collect only the config and default facts
- icx_facts:
gather_subset:
- config
# Do not collect hardware facts
- icx_facts:
gather_subset:
- "!hardware"
"""
RETURN = """
ansible_net_gather_subset:
description: The list of fact subsets collected from the device
returned: always
type: list
# default
ansible_net_model:
description: The model name returned from the device
returned: always
type: str
ansible_net_serialnum:
description: The serial number of the remote device
returned: always
type: str
ansible_net_version:
description: The operating system version running on the remote device
returned: always
type: str
ansible_net_hostname:
description: The configured hostname of the device
returned: always
type: str
ansible_net_image:
description: The image file the device is running
returned: always
type: str
ansible_net_stacked_models:
description: The model names of each device in the stack
returned: when multiple devices are configured in a stack
type: list
ansible_net_stacked_serialnums:
description: The serial numbers of each device in the stack
returned: when multiple devices are configured in a stack
type: list
# hardware
ansible_net_filesystems:
description: All file system names available on the device
returned: when hardware is configured
type: list
ansible_net_filesystems_info:
description: A hash of all file systems containing info about each file system (e.g. free and total space)
returned: when hardware is configured
type: dict
ansible_net_memfree_mb:
description: The available free memory on the remote device in Mb
returned: when hardware is configured
type: int
ansible_net_memtotal_mb:
description: The total memory on the remote device in Mb
returned: when hardware is configured
type: int
# config
ansible_net_config:
description: The current active config from the device
returned: when config is configured
type: str
# interfaces
ansible_net_all_ipv4_addresses:
description: All IPv4 addresses configured on the device
returned: when interfaces is configured
type: list
ansible_net_all_ipv6_addresses:
description: All IPv6 addresses configured on the device
returned: when interfaces is configured
type: list
ansible_net_interfaces:
description: A hash of all interfaces running on the system
returned: when interfaces is configured
type: dict
ansible_net_neighbors:
description: The list of LLDP neighbors from the remote device
returned: when interfaces is configured
type: dict
"""
import re
from ansible_collections.community.general.plugins.module_utils.network.icx.icx import run_commands
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.six import iteritems
from ansible.module_utils.six.moves import zip
class FactsBase(object):
COMMANDS = list()
def __init__(self, module):
self.module = module
self.facts = dict()
self.responses = None
def populate(self):
self.responses = run_commands(self.module, commands=self.COMMANDS, check_rc=False)
def run(self, cmd):
return run_commands(self.module, commands=cmd, check_rc=False)
class Default(FactsBase):
COMMANDS = ['show version', 'show running-config | include hostname']
def populate(self):
super(Default, self).run(['skip'])
super(Default, self).populate()
data = self.responses[0]
if data:
self.facts['version'] = self.parse_version(data)
self.facts['serialnum'] = self.parse_serialnum(data)
self.facts['model'] = self.parse_model(data)
self.facts['image'] = self.parse_image(data)
self.facts['hostname'] = self.parse_hostname(self.responses[1])
self.parse_stacks(data)
def parse_version(self, data):
match = re.search(r'SW: Version ([0-9]+.[0-9]+.[0-9a-zA-Z]+)', data)
if match:
return match.group(1)
def parse_hostname(self, data):
match = re.search(r'^hostname (\S+)', data, re.M)
if match:
return match.group(1)
def parse_model(self, data):
match = re.search(r'HW: (\S+ \S+)', data, re.M)
if match:
return match.group(1)
def parse_image(self, data):
match = re.search(r'\([0-9]+ bytes\) from \S+ (\S+)', data)
if match:
return match.group(1)
def parse_serialnum(self, data):
match = re.search(r'Serial #:(\S+)', data)
if match:
return match.group(1)
def parse_stacks(self, data):
match = re.findall(r'UNIT [1-9]+: SL [1-9]+: (\S+)', data, re.M)
if match:
self.facts['stacked_models'] = match
match = re.findall(r'^System [Ss]erial [Nn]umber\s+: (\S+)', data, re.M)
if match:
self.facts['stacked_serialnums'] = match
class Hardware(FactsBase):
COMMANDS = [
'show memory',
'show flash'
]
def populate(self):
super(Hardware, self).populate()
data = self.responses[0]
if data:
self.facts['filesystems'] = self.parse_filesystems(data)
self.facts['filesystems_info'] = self.parse_filesystems_info(self.responses[1])
if data:
if 'Invalid input detected' in data:
warnings.append('Unable to gather memory statistics')
else:
match = re.search(r'Dynamic memory: ([0-9]+) bytes total, ([0-9]+) bytes free, ([0-9]+%) used', data)
if match:
self.facts['memtotal_mb'] = int(match.group(1)) / 1024
self.facts['memfree_mb'] = int(match.group(2)) / 1024
def parse_filesystems(self, data):
return "flash"
def parse_filesystems_info(self, data):
facts = dict()
fs = ''
for line in data.split('\n'):
match = re.match(r'^(Stack unit \S+):', line)
if match:
fs = match.group(1)
facts[fs] = dict()
continue
match = re.match(r'\W+NAND Type: Micron NAND (\S+)', line)
if match:
facts[fs]['spacetotal'] = match.group(1)
match = re.match(r'\W+Code Flash Free Space = (\S+)', line)
if match:
facts[fs]['spacefree'] = int(int(match.group(1)) / 1024)
facts[fs]['spacefree'] = str(facts[fs]['spacefree']) + "Kb"
return {"flash": facts}
class Config(FactsBase):
COMMANDS = ['skip', 'show running-config']
def populate(self):
super(Config, self).populate()
data = self.responses[1]
if data:
self.facts['config'] = data
class Interfaces(FactsBase):
COMMANDS = [
'skip',
'show interfaces',
'show running-config',
'show lldp',
'show media'
]
def populate(self):
super(Interfaces, self).populate()
self.facts['all_ipv4_addresses'] = list()
self.facts['all_ipv6_addresses'] = list()
data = self.responses[1]
if data:
interfaces = self.parse_interfaces(data)
self.facts['interfaces'] = self.populate_interfaces(interfaces)
data = self.responses[1]
if data:
data = self.parse_interfaces(data)
self.populate_ipv4_interfaces(data)
data = self.responses[2]
if data:
self.populate_ipv6_interfaces(data)
data = self.responses[3]
lldp_errs = ['Invalid input', 'LLDP is not enabled']
if data and not any(err in data for err in lldp_errs):
neighbors = self.run(['show lldp neighbors detail'])
if neighbors:
self.facts['neighbors'] = self.parse_neighbors(neighbors[0])
data = self.responses[4]
self.populate_mediatype(data)
interfaceList = {}
for iface in self.facts['interfaces']:
if 'type' in self.facts['interfaces'][iface]:
newName = self.facts['interfaces'][iface]['type'] + iface
else:
newName = iface
interfaceList[newName] = self.facts['interfaces'][iface]
self.facts['interfaces'] = interfaceList
def populate_mediatype(self, data):
lines = data.split("\n")
for line in lines:
match = re.match(r'Port (\S+):\W+Type\W+:\W+(.*)', line)
if match:
self.facts['interfaces'][match.group(1)]["mediatype"] = match.group(2)
def populate_interfaces(self, interfaces):
facts = dict()
for key, value in iteritems(interfaces):
intf = dict()
intf['description'] = self.parse_description(value)
intf['macaddress'] = self.parse_macaddress(value)
intf['mtu'] = self.parse_mtu(value)
intf['bandwidth'] = self.parse_bandwidth(value)
intf['duplex'] = self.parse_duplex(value)
intf['lineprotocol'] = self.parse_lineprotocol(value)
intf['operstatus'] = self.parse_operstatus(value)
intf['type'] = self.parse_type(value)
facts[key] = intf
return facts
def populate_ipv4_interfaces(self, data):
for key, value in data.items():
self.facts['interfaces'][key]['ipv4'] = dict()
primary_address = addresses = []
primary_address = re.findall(r'Internet address is (\S+/\S+), .*$', value, re.M)
addresses = re.findall(r'Secondary address (.+)$', value, re.M)
if len(primary_address) == 0:
continue
addresses.append(primary_address[0])
for address in addresses:
addr, subnet = address.split("/")
ipv4 = dict(address=addr.strip(), subnet=subnet.strip())
self.add_ip_address(addr.strip(), 'ipv4')
self.facts['interfaces'][key]['ipv4'] = ipv4
def populate_ipv6_interfaces(self, data):
parts = data.split("\n")
for line in parts:
match = re.match(r'\W*interface \S+ (\S+)', line)
if match:
key = match.group(1)
try:
self.facts['interfaces'][key]['ipv6'] = list()
except KeyError:
self.facts['interfaces'][key] = dict()
self.facts['interfaces'][key]['ipv6'] = list()
self.facts['interfaces'][key]['ipv6'] = {}
continue
match = re.match(r'\W+ipv6 address (\S+)/(\S+)', line)
if match:
self.add_ip_address(match.group(1), "ipv6")
self.facts['interfaces'][key]['ipv6']["address"] = match.group(1)
self.facts['interfaces'][key]['ipv6']["subnet"] = match.group(2)
def add_ip_address(self, address, family):
if family == 'ipv4':
self.facts['all_ipv4_addresses'].append(address)
else:
self.facts['all_ipv6_addresses'].append(address)
def parse_neighbors(self, neighbors):
facts = dict()
for entry in neighbors.split('------------------------------------------------'):
if entry == '':
continue
intf = self.parse_lldp_intf(entry)
if intf not in facts:
facts[intf] = list()
fact = dict()
fact['host'] = self.parse_lldp_host(entry)
fact['port'] = self.parse_lldp_port(entry)
facts[intf].append(fact)
return facts
def parse_interfaces(self, data):
parsed = dict()
key = ''
for line in data.split('\n'):
if len(line) == 0:
continue
elif line[0] == ' ':
parsed[key] += '\n%s' % line
else:
match = re.match(r'\S+Ethernet(\S+)', line)
if match:
key = match.group(1)
parsed[key] = line
return parsed
def parse_description(self, data):
match = re.search(r'Port name is ([ \S]+)', data, re.M)
if match:
return match.group(1)
def parse_macaddress(self, data):
match = re.search(r'Hardware is \S+, address is (\S+)', data)
if match:
return match.group(1)
def parse_ipv4(self, data):
match = re.search(r'Internet address is (\S+)', data)
if match:
addr, masklen = match.group(1).split('/')
return dict(address=addr, masklen=int(masklen))
def parse_mtu(self, data):
match = re.search(r'MTU (\d+)', data)
if match:
return int(match.group(1))
def parse_bandwidth(self, data):
match = re.search(r'Configured speed (\S+), actual (\S+)', data)
if match:
return match.group(1)
def parse_duplex(self, data):
match = re.search(r'configured duplex (\S+), actual (\S+)', data, re.M)
if match:
return match.group(2)
def parse_mediatype(self, data):
match = re.search(r'media type is (.+)$', data, re.M)
if match:
return match.group(1)
def parse_type(self, data):
match = re.search(r'Hardware is (.+),', data, re.M)
if match:
return match.group(1)
def parse_lineprotocol(self, data):
match = re.search(r'line protocol is (.+)$', data, re.M)
if match:
return match.group(1)
def parse_operstatus(self, data):
match = re.search(r'^(?:.+) is (.+),', data, re.M)
if match:
return match.group(1)
def parse_lldp_intf(self, data):
match = re.search(r'^Local Intf: (.+)$', data, re.M)
if match:
return match.group(1)
def parse_lldp_host(self, data):
match = re.search(r'System Name: (.+)$', data, re.M)
if match:
return match.group(1)
def parse_lldp_port(self, data):
match = re.search(r'Port id: (.+)$', data, re.M)
if match:
return match.group(1)
FACT_SUBSETS = dict(
default=Default,
hardware=Hardware,
interfaces=Interfaces,
config=Config,
)
VALID_SUBSETS = frozenset(FACT_SUBSETS.keys())
warnings = list()
def main():
"""main entry point for module execution
"""
argument_spec = dict(
gather_subset=dict(default=['!config'], type='list')
)
module = AnsibleModule(argument_spec=argument_spec,
supports_check_mode=True)
gather_subset = module.params['gather_subset']
runable_subsets = set()
exclude_subsets = set()
for subset in gather_subset:
if subset == 'all':
runable_subsets.update(VALID_SUBSETS)
continue
if subset.startswith('!'):
subset = subset[1:]
if subset == 'all':
exclude_subsets.update(VALID_SUBSETS)
continue
exclude = True
else:
exclude = False
if subset not in VALID_SUBSETS:
module.fail_json(msg='Bad subset')
if exclude:
exclude_subsets.add(subset)
else:
runable_subsets.add(subset)
if not runable_subsets:
runable_subsets.update(VALID_SUBSETS)
runable_subsets.difference_update(exclude_subsets)
runable_subsets.add('default')
facts = dict()
facts['gather_subset'] = list(runable_subsets)
instances = list()
for key in runable_subsets:
instances.append(FACT_SUBSETS[key](module))
for inst in instances:
inst.populate()
facts.update(inst.facts)
ansible_facts = dict()
for key, value in iteritems(facts):
key = 'ansible_net_%s' % key
ansible_facts[key] = value
module.exit_json(ansible_facts=ansible_facts, warnings=warnings)
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,693 @@
#!/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_interface
author: "Ruckus Wireless (@Commscope)"
short_description: Manage Interface on Ruckus ICX 7000 series switches
description:
- This module provides declarative management of Interfaces
on ruckus icx devices.
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:
name:
description:
- Name of the Interface.
type: str
description:
description:
- Name of the description.
type: str
enabled:
description:
- Interface link status
default: yes
type: bool
speed:
description:
- Interface link speed/duplex
choices: ['10-full', '10-half', '100-full', '100-half', '1000-full', '1000-full-master',
'1000-full-slave', '10g-full', '10g-full-master', '10g-full-slave', '2500-full', '2500-full-master',
'2500-full-slave', '5g-full', '5g-full-master', '5g-full-slave', 'auto']
type: str
stp:
description:
- enable/disable stp for the interface
type: bool
tx_rate:
description:
- Transmit rate in bits per second (bps).
- This is state check parameter only.
- Supports conditionals, see L(Conditionals in Networking Modules,../network/user_guide/network_working_with_command_output.html)
type: str
rx_rate:
description:
- Receiver rate in bits per second (bps).
- This is state check parameter only.
- Supports conditionals, see L(Conditionals in Networking Modules,../network/user_guide/network_working_with_command_output.html)
type: str
neighbors:
description:
- Check the operational state of given interface C(name) for CDP/LLDP neighbor.
- The following suboptions are available.
type: list
suboptions:
host:
description:
- "CDP/LLDP neighbor host for given interface C(name)."
type: str
port:
description:
- "CDP/LLDP neighbor port to which given interface C(name) is connected."
type: str
delay:
description:
- Time in seconds to wait before checking for the operational state on remote
device. This wait is applicable for operational state argument which are
I(state) with values C(up)/C(down), I(tx_rate) and I(rx_rate).
type: int
default: 10
state:
description:
- State of the Interface configuration, C(up) means present and
operationally up and C(down) means present and operationally C(down)
default: present
type: str
choices: ['present', 'absent', 'up', 'down']
power:
description:
- Inline power on Power over Ethernet (PoE) ports.
type: dict
suboptions:
by_class:
description:
- "The range is 0-4"
- "The power limit based on class value for given interface C(name)"
choices: ['0', '1', '2', '3', '4']
type: str
limit:
description:
- "The range is 1000-15400|30000mW. For PoH ports the range is 1000-95000mW"
- "The power limit based on actual power value for given interface C(name)"
type: str
priority:
description:
- "The range is 1 (highest) to 3 (lowest)"
- "The priority for power management or given interface C(name)"
choices: ['1', '2', '3']
type: str
enabled:
description:
- "enable/disable the poe of the given interface C(name)"
default: no
type: bool
aggregate:
description:
- List of Interfaces definitions.
type: list
suboptions:
name:
description:
- Name of the Interface.
type: str
description:
description:
- Name of the description.
type: str
enabled:
description:
- Interface link status
type: bool
speed:
description:
- Interface link speed/duplex
choices: ['10-full', '10-half', '100-full', '100-half', '1000-full', '1000-full-master',
'1000-full-slave', '10g-full', '10g-full-master', '10g-full-slave', '2500-full', '2500-full-master',
'2500-full-slave', '5g-full', '5g-full-master', '5g-full-slave', 'auto']
type: str
stp:
description:
- enable/disable stp for the interface
type: bool
tx_rate:
description:
- Transmit rate in bits per second (bps).
- This is state check parameter only.
- Supports conditionals, see L(Conditionals in Networking Modules,../network/user_guide/network_working_with_command_output.html)
type: str
rx_rate:
description:
- Receiver rate in bits per second (bps).
- This is state check parameter only.
- Supports conditionals, see L(Conditionals in Networking Modules,../network/user_guide/network_working_with_command_output.html)
type: str
neighbors:
description:
- Check the operational state of given interface C(name) for CDP/LLDP neighbor.
- The following suboptions are available.
type: list
suboptions:
host:
description:
- "CDP/LLDP neighbor host for given interface C(name)."
type: str
port:
description:
- "CDP/LLDP neighbor port to which given interface C(name) is connected."
type: str
delay:
description:
- Time in seconds to wait before checking for the operational state on remote
device. This wait is applicable for operational state argument which are
I(state) with values C(up)/C(down), I(tx_rate) and I(rx_rate).
type: int
state:
description:
- State of the Interface configuration, C(up) means present and
operationally up and C(down) means present and operationally C(down)
type: str
choices: ['present', 'absent', 'up', 'down']
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
power:
description:
- Inline power on Power over Ethernet (PoE) ports.
type: dict
suboptions:
by_class:
description:
- "The range is 0-4"
- "The power limit based on class value for given interface C(name)"
choices: ['0', '1', '2', '3', '4']
type: str
limit:
description:
- "The range is 1000-15400|30000mW. For PoH ports the range is 1000-95000mW"
- "The power limit based on actual power value for given interface C(name)"
type: str
priority:
description:
- "The range is 1 (highest) to 3 (lowest)"
- "The priority for power management or given interface C(name)"
choices: ['1', '2', '3']
type: str
enabled:
description:
- "enable/disable the poe of the given interface C(name)"
type: bool
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.
default: yes
type: bool
'''
EXAMPLES = """
- name: enable ethernet port and set name
icx_interface:
name: ethernet 1/1/1
description: interface-1
stp: true
enabled: true
- name: disable ethernet port 1/1/1
icx_interface:
name: ethernet 1/1/1
enabled: false
- name: enable ethernet port range, set name and speed.
icx_interface:
name: ethernet 1/1/1 to 1/1/10
description: interface-1
speed: 100-full
enabled: true
- name: enable poe. Set class.
icx_interface:
name: ethernet 1/1/1
power:
by_class: 2
- name: configure poe limit of interface
icx_interface:
name: ethernet 1/1/1
power:
limit: 10000
- name: disable poe of interface
icx_interface:
name: ethernet 1/1/1
power:
enabled: false
- name: set lag name for a range of lags
icx_interface:
name: lag 1 to 10
description: test lags
- name: Disable lag
icx_interface:
name: lag 1
enabled: false
- name: enable management interface
icx_interface:
name: management 1
enabled: true
- name: enable loopback interface
icx_interface:
name: loopback 10
enabled: true
- name: Add interface using aggregate
icx_interface:
aggregate:
- { name: ethernet 1/1/1, description: test-interface-1, power: { by_class: 2 } }
- { name: ethernet 1/1/3, description: test-interface-3}
speed: 10-full
enabled: true
- name: Check tx_rate, rx_rate intent arguments
icx_interface:
name: ethernet 1/1/10
state: up
tx_rate: ge(0)
rx_rate: le(0)
- name: Check neighbors intent arguments
icx_interface:
name: ethernet 1/1/10
neighbors:
- port: 1/1/5
host: netdev
"""
RETURN = """
commands:
description: The list of configuration mode commands to send to the device.
returned: always
type: list
sample:
- interface ethernet 1/1/1
- port-name interface-1
- state present
- speed-duplex 100-full
- inline power priority 1
"""
import re
from copy import deepcopy
from time import sleep
from ansible.module_utils._text import to_text
from ansible.module_utils.basic import AnsibleModule, env_fallback
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.config import NetworkConfig
from ansible_collections.community.general.plugins.module_utils.network.icx.icx import load_config, get_config
from ansible.module_utils.connection import Connection, ConnectionError, exec_command
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import conditional, remove_default_spec
def parse_enable(configobj, name):
cfg = configobj['interface %s' % name]
cfg = '\n'.join(cfg.children)
match = re.search(r'^disable', cfg, re.M)
if match:
return True
else:
return False
def parse_power_argument(configobj, name):
cfg = configobj['interface %s' % name]
cfg = '\n'.join(cfg.children)
match = re.search(r'(^inline power|^inline power(.*))+$', cfg, re.M)
if match:
return match.group(1)
def parse_config_argument(configobj, name, arg=None):
cfg = configobj['interface %s' % name]
cfg = '\n'.join(cfg.children)
match = re.search(r'%s (.+)$' % arg, cfg, re.M)
if match:
return match.group(1)
def parse_stp_arguments(module, item):
rc, out, err = exec_command(module, 'show interfaces ' + item)
match = re.search(r'STP configured to (\S+),', out, re.S)
if match:
return True if match.group(1) == "ON" else False
def search_obj_in_list(name, lst):
for o in lst:
if o['name'] == name:
return o
return None
def validate_power(module, power):
count = 0
for item in power:
if power.get(item) is not None:
count += 1
if count > 1:
module.fail_json(msg='power parameters are mutually exclusive: class,limit,priority,enabled')
def add_command_to_interface(interface, cmd, commands):
if interface not in commands:
commands.append(interface)
commands.append(cmd)
def map_config_to_obj(module):
compare = module.params['check_running_config']
config = get_config(module, None, compare)
configobj = NetworkConfig(indent=1, contents=config)
match = re.findall(r'^interface (.+)$', config, re.M)
if not match:
return list()
instances = list()
for item in set(match):
obj = {
'name': item,
'port-name': parse_config_argument(configobj, item, 'port-name'),
'speed-duplex': parse_config_argument(configobj, item, 'speed-duplex'),
'stp': parse_stp_arguments(module, item),
'disable': True if parse_enable(configobj, item) else False,
'power': parse_power_argument(configobj, item),
'state': 'present'
}
instances.append(obj)
return instances
def parse_poe_config(poe, power):
if poe.get('by_class') is not None:
power += 'power-by-class %s' % poe.get('by_class')
elif poe.get('limit') is not None:
power += 'power-limit %s' % poe.get('limit')
elif poe.get('priority') is not None:
power += 'priority %s' % poe.get('priority')
elif poe.get('enabled'):
power = 'inline power'
elif poe.get('enabled') is False:
power = 'no inline power'
return power
def map_params_to_obj(module):
obj = []
aggregate = module.params.get('aggregate')
if aggregate:
for item in aggregate:
for key in item:
if item.get(key) is None:
item[key] = module.params[key]
item['port-name'] = item.pop('description')
item['speed-duplex'] = item.pop('speed')
poe = item.get('power')
if poe:
validate_power(module, poe)
power = 'inline power' + ' '
power_arg = parse_poe_config(poe, power)
item.update({'power': power_arg})
d = item.copy()
if d['enabled']:
d['disable'] = False
else:
d['disable'] = True
obj.append(d)
else:
params = {
'name': module.params['name'],
'port-name': module.params['description'],
'speed-duplex': module.params['speed'],
'stp': module.params['stp'],
'delay': module.params['delay'],
'state': module.params['state'],
'tx_rate': module.params['tx_rate'],
'rx_rate': module.params['rx_rate'],
'neighbors': module.params['neighbors']
}
poe = module.params.get('power')
if poe:
validate_power(module, poe)
power = 'inline power' + ' '
power_arg = parse_poe_config(poe, power)
params.update({'power': power_arg})
if module.params['enabled']:
params.update({'disable': False})
else:
params.update({'disable': True})
obj.append(params)
return obj
def map_obj_to_commands(updates):
commands = list()
want, have = updates
args = ('speed-duplex', 'port-name', 'power', 'stp')
for w in want:
name = w['name']
disable = w['disable']
state = w['state']
obj_in_have = search_obj_in_list(name, have)
interface = 'interface ' + name
if state == 'absent' and have == []:
commands.append('no ' + interface)
elif state == 'absent' and obj_in_have:
commands.append('no ' + interface)
elif state in ('present', 'up', 'down'):
if obj_in_have:
for item in args:
candidate = w.get(item)
running = obj_in_have.get(item)
if candidate == 'no inline power' and running is None:
candidate = None
if candidate != running:
if candidate:
if item == 'power':
cmd = str(candidate)
elif item == 'stp':
cmd = 'spanning-tree' if candidate else 'no spanning-tree'
else:
cmd = item + ' ' + str(candidate)
add_command_to_interface(interface, cmd, commands)
if disable and not obj_in_have.get('disable', False):
add_command_to_interface(interface, 'disable', commands)
elif not disable and obj_in_have.get('disable', False):
add_command_to_interface(interface, 'enable', commands)
else:
commands.append(interface)
for item in args:
value = w.get(item)
if value:
if item == 'power':
commands.append(str(value))
elif item == 'stp':
cmd = 'spanning-tree' if item else 'no spanning-tree'
else:
commands.append(item + ' ' + str(value))
if disable:
commands.append('disable')
if disable is False:
commands.append('enable')
return commands
def check_declarative_intent_params(module, want, result):
failed_conditions = []
have_neighbors_lldp = None
have_neighbors_cdp = None
for w in want:
want_state = w.get('state')
want_tx_rate = w.get('tx_rate')
want_rx_rate = w.get('rx_rate')
want_neighbors = w.get('neighbors')
if want_state not in ('up', 'down') and not want_tx_rate and not want_rx_rate and not want_neighbors:
continue
if result['changed']:
sleep(w['delay'])
command = 'show interfaces %s' % w['name']
rc, out, err = exec_command(module, command)
if rc != 0:
module.fail_json(msg=to_text(err, errors='surrogate_then_replace'), command=command, rc=rc)
if want_state in ('up', 'down'):
match = re.search(r'%s (\w+)' % 'line protocol is', out, re.M)
have_state = None
if match:
have_state = match.group(1)
if have_state is None or not conditional(want_state, have_state.strip()):
failed_conditions.append('state ' + 'eq(%s)' % want_state)
if want_tx_rate:
match = re.search(r'%s (\d+)' % 'output rate:', out, re.M)
have_tx_rate = None
if match:
have_tx_rate = match.group(1)
if have_tx_rate is None or not conditional(want_tx_rate, have_tx_rate.strip(), cast=int):
failed_conditions.append('tx_rate ' + want_tx_rate)
if want_rx_rate:
match = re.search(r'%s (\d+)' % 'input rate:', out, re.M)
have_rx_rate = None
if match:
have_rx_rate = match.group(1)
if have_rx_rate is None or not conditional(want_rx_rate, have_rx_rate.strip(), cast=int):
failed_conditions.append('rx_rate ' + want_rx_rate)
if want_neighbors:
have_host = []
have_port = []
if have_neighbors_lldp is None:
rc, have_neighbors_lldp, err = exec_command(module, 'show lldp neighbors detail')
if rc != 0:
module.fail_json(msg=to_text(err, errors='surrogate_then_replace'), command=command, rc=rc)
if have_neighbors_lldp:
lines = have_neighbors_lldp.strip().split('Local port: ')
for line in lines:
field = line.split('\n')
if field[0].strip() == w['name'].split(' ')[1]:
for item in field:
match = re.search(r'\s*\+\s+System name\s+:\s+"(.*)"', item, re.M)
if match:
have_host.append(match.group(1))
match = re.search(r'\s*\+\s+Port description\s+:\s+"(.*)"', item, re.M)
if match:
have_port.append(match.group(1))
for item in want_neighbors:
host = item.get('host')
port = item.get('port')
if host and host not in have_host:
failed_conditions.append('host ' + host)
if port and port not in have_port:
failed_conditions.append('port ' + port)
return failed_conditions
def main():
""" main entry point for module execution
"""
power_spec = dict(
by_class=dict(choices=['0', '1', '2', '3', '4']),
limit=dict(type='str'),
priority=dict(choices=['1', '2', '3']),
enabled=dict(type='bool')
)
neighbors_spec = dict(
host=dict(),
port=dict()
)
element_spec = dict(
name=dict(),
description=dict(),
enabled=dict(default=True, type='bool'),
speed=dict(type='str', choices=['10-full', '10-half', '100-full', '100-half', '1000-full', '1000-full-master',
'1000-full-slave', '10g-full', '10g-full-master', '10g-full-slave', '2500-full', '2500-full-master',
'2500-full-slave', '5g-full', '5g-full-master', '5g-full-slave', 'auto']),
stp=dict(type='bool'),
tx_rate=dict(),
rx_rate=dict(),
neighbors=dict(type='list', elements='dict', options=neighbors_spec),
delay=dict(default=10, type='int'),
state=dict(default='present',
choices=['present', 'absent', 'up', 'down']),
power=dict(type='dict', options=power_spec),
check_running_config=dict(default=True, type='bool', fallback=(env_fallback, ['ANSIBLE_CHECK_ICX_RUNNING_CONFIG']))
)
aggregate_spec = deepcopy(element_spec)
aggregate_spec['name'] = dict(required=True)
remove_default_spec(aggregate_spec)
argument_spec = dict(
aggregate=dict(type='list', elements='dict', options=aggregate_spec),
)
argument_spec.update(element_spec)
required_one_of = [['name', 'aggregate']]
mutually_exclusive = [['name', 'aggregate']]
module = AnsibleModule(argument_spec=argument_spec,
required_one_of=required_one_of,
mutually_exclusive=mutually_exclusive,
supports_check_mode=True)
warnings = list()
result = {}
result['changed'] = False
if warnings:
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))
result['commands'] = commands
if commands:
if not module.check_mode:
load_config(module, commands)
result['changed'] = True
failed_conditions = check_declarative_intent_params(module, want, result)
if failed_conditions:
msg = 'One or more conditional statements have not been satisfied'
module.fail_json(msg=msg, failed_conditions=failed_conditions)
module.exit_json(**result)
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,438 @@
#!/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_l3_interface
author: "Ruckus Wireless (@Commscope)"
short_description: Manage Layer-3 interfaces on Ruckus ICX 7000 series switches
description:
- This module provides declarative management of Layer-3 interfaces
on ICX network devices.
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:
name:
description:
- Name of the Layer-3 interface to be configured eg. GigabitEthernet0/2, ve 10, ethernet 1/1/1
type: str
ipv4:
description:
- IPv4 address to be set for the Layer-3 interface mentioned in I(name) option.
The address format is <ipv4 address>/<mask>, the mask is number
in range 0-32 eg. 192.168.0.1/24
type: str
ipv6:
description:
- IPv6 address to be set for the Layer-3 interface mentioned in I(name) option.
The address format is <ipv6 address>/<mask>, the mask is number
in range 0-128 eg. fd5d:12c9:2201:1::1/64.
type: str
mode:
description:
- Specifies if ipv4 address should be dynamic/advertise to ospf/not advertise to ospf.
This should be specified only if ipv4 address is configured and if it is not secondary IP address.
choices: ['dynamic', 'ospf-ignore', 'ospf-passive']
type: str
replace:
description:
- Replaces the configured primary IP address on the interface.
choices: ['yes', 'no']
type: str
secondary:
description:
- Specifies that the configured address is a secondary IP address.
If this keyword is omitted, the configured address is the primary IP address.
choices: ['yes', 'no']
type: str
aggregate:
description:
- List of Layer-3 interfaces definitions. Each of the entry in aggregate list should
define name of interface C(name) and a optional C(ipv4) or C(ipv6) address.
type: list
suboptions:
name:
description:
- Name of the Layer-3 interface to be configured eg. GigabitEthernet0/2, ve 10, ethernet 1/1/1
type: str
ipv4:
description:
- IPv4 address to be set for the Layer-3 interface mentioned in I(name) option.
The address format is <ipv4 address>/<mask>, the mask is number
in range 0-32 eg. 192.168.0.1/24
type: str
ipv6:
description:
- IPv6 address to be set for the Layer-3 interface mentioned in I(name) option.
The address format is <ipv6 address>/<mask>, the mask is number
in range 0-128 eg. fd5d:12c9:2201:1::1/64.
type: str
mode:
description:
- Specifies if ipv4 address should be dynamic/advertise to ospf/not advertise to ospf.
This should be specified only if ipv4 address is configured and if it is not secondary IP address.
choices: ['dynamic', 'ospf-ignore', 'ospf-passive']
type: str
replace:
description:
- Replaces the configured primary IP address on the interface.
choices: ['yes', 'no']
type: str
secondary:
description:
- Specifies that the configured address is a secondary IP address.
If this keyword is omitted, the configured address is the primary IP address.
choices: ['yes', 'no']
type: str
state:
description:
- State of the Layer-3 interface configuration. It indicates if the configuration should
be present or absent on remote device.
choices: ['present', 'absent']
type: str
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
state:
description:
- State of the Layer-3 interface configuration. It indicates if the configuration should
be present or absent on remote device.
default: present
choices: ['present', 'absent']
type: str
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: Remove ethernet 1/1/1 IPv4 and IPv6 address
icx_l3_interface:
name: ethernet 1/1/1
ipv4: 192.168.0.1/24
ipv6: "fd5d:12c9:2201:1::1/64"
state: absent
- name: Replace ethernet 1/1/1 primary IPv4 address
icx_l3_interface:
name: ethernet 1/1/1
ipv4: 192.168.0.1/24
replace: yes
state: absent
- name: Replace ethernet 1/1/1 dynamic IPv4 address
icx_l3_interface:
name: ethernet 1/1/1
ipv4: 192.168.0.1/24
mode: dynamic
state: absent
- name: Set ethernet 1/1/1 secondary IPv4 address
icx_l3_interface:
name: ethernet 1/1/1
ipv4: 192.168.0.1/24
secondary: yes
state: absent
- name: Set ethernet 1/1/1 IPv4 address
icx_l3_interface:
name: ethernet 1/1/1
ipv4: 192.168.0.1/24
- name: Set ethernet 1/1/1 IPv6 address
icx_l3_interface:
name: ethernet 1/1/1
ipv6: "fd5d:12c9:2201:1::1/64"
- name: Set IP addresses on aggregate
icx_l3_interface:
aggregate:
- { name: GigabitEthernet0/3, ipv4: 192.168.2.10/24 }
- { name: GigabitEthernet0/3, ipv4: 192.168.3.10/24, ipv6: "fd5d:12c9:2201:1::1/64" }
- name: Remove IP addresses on aggregate
icx_l3_interface:
aggregate:
- { name: GigabitEthernet0/3, ipv4: 192.168.2.10/24 }
- { name: GigabitEthernet0/3, ipv4: 192.168.3.10/24, ipv6: "fd5d:12c9:2201:1::1/64" }
state: absent
- name: Set the ipv4 and ipv6 of a virtual ethernet(ve)
icx_l3_interface:
name: ve 100
ipv4: 192.168.0.1
ipv6: "2001:0db8:85a3:0000:0000:8a2e:0370:7334"
"""
RETURN = """
commands:
description: The list of configuration mode commands to send to the device
returned: always, except for the platforms that use Netconf transport to manage the device.
type: list
sample:
- interface ethernet 1/1/1
- ip address 192.168.0.1 255.255.255.0
- ipv6 address fd5d:12c9:2201:1::1/64
"""
import re
from copy import deepcopy
from ansible.module_utils._text import to_text
from ansible.module_utils.basic import AnsibleModule, env_fallback
from ansible.module_utils.connection import exec_command
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.config import NetworkConfig
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import remove_default_spec
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import is_netmask, is_masklen, to_netmask, to_masklen
def validate_ipv4(value, module):
if value:
address = value.split('/')
if len(address) != 2:
module.fail_json(msg='address format is <ipv4 address>/<mask>, got invalid format %s' % value)
else:
if not is_masklen(address[1]):
module.fail_json(msg='invalid value for mask: %s, mask should be in range 0-32' % address[1])
def validate_ipv6(value, module):
if value:
address = value.split('/')
if len(address) != 2:
module.fail_json(msg='address format is <ipv6 address>/<mask>, got invalid format %s' % value)
else:
if not 0 <= int(address[1]) <= 128:
module.fail_json(msg='invalid value for mask: %s, mask should be in range 0-128' % address[1])
def validate_param_values(module, obj, param=None):
if param is None:
param = module.params
for key in obj:
validator = globals().get('validate_%s' % key)
if callable(validator):
validator(param.get(key), module)
def map_params_to_obj(module):
obj = []
aggregate = module.params.get('aggregate')
if aggregate:
for item in aggregate:
for key in item:
if item.get(key) is None:
item[key] = module.params[key]
validate_param_values(module, item, item)
obj.append(item.copy())
else:
obj.append({
'name': module.params['name'],
'ipv4': module.params['ipv4'],
'ipv6': module.params['ipv6'],
'state': module.params['state'],
'replace': module.params['replace'],
'mode': module.params['mode'],
'secondary': module.params['secondary'],
})
validate_param_values(module, obj)
return obj
def parse_config_argument(configobj, name, arg=None):
cfg = configobj['interface %s' % name]
cfg = '\n'.join(cfg.children)
values = []
matches = re.finditer(r'%s (.+)$' % arg, cfg, re.M)
for match in matches:
match_str = match.group(1).strip()
if arg == 'ipv6 address':
values.append(match_str)
else:
values = match_str
break
return values or None
def search_obj_in_list(name, lst):
for o in lst:
if o['name'] == name:
return o
return None
def map_config_to_obj(module):
compare = module.params['check_running_config']
config = get_config(module, flags=['| begin interface'], compare=compare)
configobj = NetworkConfig(indent=1, contents=config)
match = re.findall(r'^interface (\S+ \S+)', config, re.M)
if not match:
return list()
instances = list()
for item in set(match):
ipv4 = parse_config_argument(configobj, item, 'ip address')
if ipv4:
address = ipv4.strip().split(' ')
if len(address) == 2 and is_netmask(address[1]):
ipv4 = '{0}/{1}'.format(address[0], to_text(to_masklen(address[1])))
obj = {
'name': item,
'ipv4': ipv4,
'ipv6': parse_config_argument(configobj, item, 'ipv6 address'),
'state': 'present'
}
instances.append(obj)
return instances
def map_obj_to_commands(updates, module):
commands = list()
want, have = updates
for w in want:
name = w['name']
ipv4 = w['ipv4']
ipv6 = w['ipv6']
state = w['state']
if 'replace' in w:
replace = w['replace'] == 'yes'
else:
replace = False
if w['mode'] is not None:
mode = ' ' + w['mode']
else:
mode = ''
if w['secondary'] is not None:
secondary = w['secondary'] == 'yes'
else:
secondary = False
interface = 'interface ' + name
commands.append(interface)
obj_in_have = search_obj_in_list(name, have)
if state == 'absent' and have == []:
if ipv4:
address = ipv4.split('/')
if len(address) == 2:
ipv4 = '{addr} {mask}'.format(addr=address[0], mask=to_netmask(address[1]))
commands.append('no ip address {ip}'.format(ip=ipv4))
if ipv6:
commands.append('no ipv6 address {ip}'.format(ip=ipv6))
elif state == 'absent' and obj_in_have:
if obj_in_have['ipv4']:
if ipv4:
address = ipv4.split('/')
if len(address) == 2:
ipv4 = '{addr} {mask}'.format(addr=address[0], mask=to_netmask(address[1]))
commands.append('no ip address {ip}'.format(ip=ipv4))
if obj_in_have['ipv6']:
if ipv6:
commands.append('no ipv6 address {ip}'.format(ip=ipv6))
elif state == 'present':
if ipv4:
if obj_in_have is None or obj_in_have.get('ipv4') is None or ipv4 != obj_in_have['ipv4']:
address = ipv4.split('/')
if len(address) == 2:
ipv4 = '{0} {1}'.format(address[0], to_netmask(address[1]))
commands.append('ip address %s%s%s%s' % (format(ipv4), mode, ' replace' if (replace) else '', ' secondary' if (secondary) else ''))
if ipv6:
if obj_in_have is None or obj_in_have.get('ipv6') is None or ipv6.lower() not in [addr.lower() for addr in obj_in_have['ipv6']]:
commands.append('ipv6 address {ip}'.format(ip=ipv6))
if commands[-1] == interface:
commands.pop(-1)
else:
commands.append("exit")
return commands
def main():
""" main entry point for module execution
"""
element_spec = dict(
name=dict(),
ipv4=dict(),
ipv6=dict(),
replace=dict(choices=['yes', 'no']),
mode=dict(choices=['dynamic', 'ospf-ignore', 'ospf-passive']),
secondary=dict(choices=['yes', 'no']),
check_running_config=dict(default=True, type='bool', fallback=(env_fallback, ['ANSIBLE_CHECK_ICX_RUNNING_CONFIG'])),
state=dict(default='present',
choices=['present', 'absent']),
)
aggregate_spec = deepcopy(element_spec)
aggregate_spec['name'] = dict(required=True)
remove_default_spec(aggregate_spec)
argument_spec = dict(
aggregate=dict(type='list', elements='dict', options=aggregate_spec)
)
argument_spec.update(element_spec)
required_one_of = [['name', 'aggregate']]
mutually_exclusive = [['name', 'aggregate'], ['secondary', 'replace'], ['secondary', 'mode']]
module = AnsibleModule(argument_spec=argument_spec,
required_one_of=required_one_of,
mutually_exclusive=mutually_exclusive,
supports_check_mode=True)
warnings = list()
result = {'changed': False}
exec_command(module, 'skip')
want = map_params_to_obj(module)
have = map_config_to_obj(module)
commands = map_obj_to_commands((want, have), module)
if commands:
if not module.check_mode:
resp = load_config(module, commands)
warnings.extend((out for out in resp if out))
result['changed'] = True
if warnings:
result['warnings'] = warnings
result['commands'] = commands
module.exit_json(**result)
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,327 @@
#!/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_linkagg
author: "Ruckus Wireless (@Commscope)"
short_description: Manage link aggregation groups on Ruckus ICX 7000 series switches
description:
- This module provides declarative management of link aggregation groups
on Ruckus ICX network devices.
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:
group:
description:
- Channel-group number for the port-channel
Link aggregation group. Range 1-255 or set to 'auto' to auto-generates a LAG ID
type: int
name:
description:
- Name of the LAG
type: str
mode:
description:
- Mode of the link aggregation group.
type: str
choices: ['dynamic', 'static']
members:
description:
- List of port members or ranges of the link aggregation group.
type: list
state:
description:
- State of the link aggregation group.
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
aggregate:
description:
- List of link aggregation definitions.
type: list
suboptions:
group:
description:
- Channel-group number for the port-channel
Link aggregation group. Range 1-255 or set to 'auto' to auto-generates a LAG ID
type: int
name:
description:
- Name of the LAG
type: str
mode:
description:
- Mode of the link aggregation group.
type: str
choices: ['dynamic', 'static']
members:
description:
- List of port members or ranges of the link aggregation group.
type: list
state:
description:
- State of the link aggregation group.
type: str
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
purge:
description:
- Purge links not defined in the I(aggregate) parameter.
type: bool
default: no
'''
EXAMPLES = """
- name: create static link aggregation group
icx_linkagg:
group: 10
mode: static
name: LAG1
- name: create link aggregation group with auto id
icx_linkagg:
group: auto
mode: dynamic
name: LAG2
- name: delete link aggregation group
icx_linkagg:
group: 10
state: absent
- name: Set members to LAG
icx_linkagg:
group: 200
mode: static
members:
- ethernet 1/1/1 to 1/1/6
- ethernet 1/1/10
- name: Remove links other then LAG id 100 and 3 using purge
icx_linkagg:
aggregate:
- { group: 3}
- { group: 100}
purge: true
"""
RETURN = """
commands:
description: The list of configuration mode commands to send to the device
returned: always, except for the platforms that use Netconf transport to manage the device.
type: list
sample:
- lag LAG1 dynamic id 11
- ports ethernet 1/1/1 to 1/1/6
- no ports ethernet 1/1/10
- no lag LAG1 dynamic id 12
"""
import re
from copy import deepcopy
from ansible.module_utils._text import to_text
from ansible.module_utils.basic import AnsibleModule, env_fallback
from ansible.module_utils.connection import ConnectionError, exec_command
from ansible_collections.community.general.plugins.module_utils.network.icx.icx import run_commands, get_config, load_config
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.config import CustomNetworkConfig
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import remove_default_spec
def range_to_members(ranges, prefix=""):
match = re.findall(r'(ethe[a-z]* [0-9]/[0-9]/[0-9]+)( to [0-9]/[0-9]/[0-9]+)?', ranges)
members = list()
for m in match:
start, end = m
if(end == ''):
start = start.replace("ethe ", "ethernet ")
members.append("%s%s" % (prefix, start))
else:
start_tmp = re.search(r'[0-9]/[0-9]/([0-9]+)', start)
end_tmp = re.search(r'[0-9]/[0-9]/([0-9]+)', end)
start = int(start_tmp.group(1))
end = int(end_tmp.group(1)) + 1
for num in range(start, end):
members.append("%sethernet 1/1/%s" % (prefix, num))
return members
def map_config_to_obj(module):
objs = dict()
compare = module.params['check_running_config']
config = get_config(module, None, compare=compare)
obj = None
for line in config.split('\n'):
l = line.strip()
match1 = re.search(r'lag (\S+) (\S+) id (\S+)', l, re.M)
if match1:
obj = dict()
obj['name'] = match1.group(1)
obj['mode'] = match1.group(2)
obj['group'] = match1.group(3)
obj['state'] = 'present'
obj['members'] = list()
else:
match2 = re.search(r'ports .*', l, re.M)
if match2 and obj is not None:
obj['members'].extend(range_to_members(match2.group(0)))
elif obj is not None:
objs[obj['group']] = obj
obj = None
return objs
def map_params_to_obj(module):
obj = []
aggregate = module.params.get('aggregate')
if aggregate:
for item in aggregate:
for key in item:
if item.get(key) is None:
item[key] = module.params[key]
d = item.copy()
d['group'] = str(d['group'])
obj.append(d)
else:
obj.append({
'group': str(module.params['group']),
'mode': module.params['mode'],
'members': module.params['members'],
'state': module.params['state'],
'name': module.params['name']
})
return obj
def search_obj_in_list(group, lst):
for o in lst:
if o['group'] == group:
return o
return None
def is_member(member, lst):
for li in lst:
ml = range_to_members(li)
if member in ml:
return True
return False
def map_obj_to_commands(updates, module):
commands = list()
want, have = updates
purge = module.params['purge']
for w in want:
if have == {} and w['state'] == 'absent':
commands.append("%slag %s %s id %s" % ('no ' if w['state'] == 'absent' else '', w['name'], w['mode'], w['group']))
elif have.get(w['group']) is None:
commands.append("%slag %s %s id %s" % ('no ' if w['state'] == 'absent' else '', w['name'], w['mode'], w['group']))
if(w.get('members') is not None and w['state'] == 'present'):
for m in w['members']:
commands.append("ports %s" % (m))
if w['state'] == 'present':
commands.append("exit")
else:
commands.append("%slag %s %s id %s" % ('no ' if w['state'] == 'absent' else '', w['name'], w['mode'], w['group']))
if(w.get('members') is not None and w['state'] == 'present'):
for m in have[w['group']]['members']:
if not is_member(m, w['members']):
commands.append("no ports %s" % (m))
for m in w['members']:
sm = range_to_members(ranges=m)
for smm in sm:
if smm not in have[w['group']]['members']:
commands.append("ports %s" % (smm))
if w['state'] == 'present':
commands.append("exit")
if purge:
for h in have:
if search_obj_in_list(have[h]['group'], want) is None:
commands.append("no lag %s %s id %s" % (have[h]['name'], have[h]['mode'], have[h]['group']))
return commands
def main():
element_spec = dict(
group=dict(type='int'),
name=dict(type='str'),
mode=dict(choices=['dynamic', 'static']),
members=dict(type='list'),
state=dict(default='present',
choices=['present', 'absent']),
check_running_config=dict(default=True, type='bool', fallback=(env_fallback, ['ANSIBLE_CHECK_ICX_RUNNING_CONFIG']))
)
aggregate_spec = deepcopy(element_spec)
aggregate_spec['group'] = dict(required=True, type='int')
required_one_of = [['group', 'aggregate']]
required_together = [['name', 'group']]
mutually_exclusive = [['group', 'aggregate']]
remove_default_spec(aggregate_spec)
argument_spec = dict(
aggregate=dict(type='list', elements='dict', options=aggregate_spec, required_together=required_together),
purge=dict(default=False, type='bool')
)
argument_spec.update(element_spec)
module = AnsibleModule(argument_spec=argument_spec,
required_one_of=required_one_of,
required_together=required_together,
mutually_exclusive=mutually_exclusive,
supports_check_mode=True)
warnings = list()
result = {'changed': False}
exec_command(module, 'skip')
if warnings:
result['warnings'] = warnings
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()

View File

@@ -0,0 +1,183 @@
#!/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_lldp
author: "Ruckus Wireless (@Commscope)"
short_description: Manage LLDP configuration on Ruckus ICX 7000 series switches
description:
- This module provides declarative management of LLDP service on ICX network devices.
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:
interfaces:
description:
- specify interfaces
suboptions:
name:
description:
- List of ethernet ports to enable lldp. To add a range of ports use 'to' keyword. See the example.
type: list
state:
description:
- State of lldp configuration for interfaces
type: str
choices: ['present', 'absent', 'enabled', 'disabled']
type: list
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
state:
description:
- Enables the receipt and transmission of Link Layer Discovery Protocol (LLDP) globally.
type: str
choices: ['present', 'absent', 'enabled', 'disabled']
'''
EXAMPLES = """
- name: Disable LLDP
icx_lldp:
state: absent
- name: Enable LLDP
icx_lldp:
state: present
- name: Disable LLDP on ports 1/1/1 - 1/1/10, 1/1/20
icx_lldp:
interfaces:
- name:
- ethernet 1/1/1 to 1/1/10
- ethernet 1/1/20
state: absent
state: present
- name: Enable LLDP on ports 1/1/5 - 1/1/10
icx_lldp:
interfaces:
- name:
- ethernet 1/1/1 to 1/1/10
"""
RETURN = """
commands:
description: The list of configuration mode commands to send to the device
returned: always, except for the platforms that use Netconf transport to manage the device.
type: list
sample:
- lldp run
- no lldp run
"""
from ansible.module_utils.basic import AnsibleModule, env_fallback
from ansible_collections.community.general.plugins.module_utils.network.icx.icx import load_config, run_commands
def has_lldp(module):
run_commands(module, ['skip'])
output = run_commands(module, ['show lldp'])
is_lldp_enable = False
if len(output) > 0 and "LLDP is not running" not in output[0]:
is_lldp_enable = True
return is_lldp_enable
def map_obj_to_commands(module, commands):
interfaces = module.params.get('interfaces')
for item in interfaces:
state = item.get('state')
if state == 'present':
for port in item.get('name'):
if 'all' in port:
module.fail_json(msg='cannot enable on all the ports')
else:
commands.append('lldp enable ports {0}'.format(str(port)))
elif state == 'absent':
for port in item.get('name'):
if 'all' in port:
module.fail_json(msg='cannot enable on all the ports')
else:
commands.append('no lldp enable ports {0}'.format(str(port)))
def main():
""" main entry point for module execution
"""
interfaces_spec = dict(
name=dict(type='list'),
state=dict(choices=['present', 'absent',
'enabled', 'disabled'])
)
argument_spec = dict(
interfaces=dict(type='list', elements='dict', options=interfaces_spec),
state=dict(choices=['present', 'absent',
'enabled', 'disabled']),
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)
warnings = list()
result = {'changed': False}
if warnings:
result['warnings'] = warnings
if module.params['check_running_config'] is False:
HAS_LLDP = None
else:
HAS_LLDP = has_lldp(module)
commands = []
state = module.params['state']
if state is None:
if HAS_LLDP:
map_obj_to_commands(module, commands)
else:
module.fail_json(msg='LLDP is not running')
else:
if state == 'absent' and HAS_LLDP is None:
commands.append('no lldp run')
if state == 'absent' and HAS_LLDP:
commands.append('no lldp run')
elif state == 'present':
if not HAS_LLDP:
commands.append('lldp run')
if module.params.get('interfaces'):
map_obj_to_commands(module, commands)
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()

View File

@@ -0,0 +1,581 @@
#!/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_logging
author: "Ruckus Wireless (@Commscope)"
short_description: Manage logging on Ruckus ICX 7000 series switches
description:
- This module provides declarative management of logging
on Ruckus ICX 7000 series switches.
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:
dest:
description:
- Destination of the logs.
choices: ['on', 'host', 'console', 'buffered', 'persistence', 'rfc5424']
type: str
name:
description:
- ipv4 address/ipv6 address/name of syslog server.
type: str
udp_port:
description:
- UDP port of destination host(syslog server).
type: str
facility:
description:
- Specifies log facility to log messages from the device.
choices: ['auth','cron','daemon','kern','local0', 'local1', 'local2', 'local3', 'local4', 'local5', 'local6', 'local7', 'user',
'lpr','mail','news','syslog','sys9','sys10','sys11','sys12','sys13','sys14','user','uucp']
type: str
level:
description:
- Specifies the message level.
type: list
choices: ['alerts', 'critical', 'debugging', 'emergencies', 'errors', 'informational',
'notifications', 'warnings']
aggregate:
description:
- List of logging definitions.
type: list
suboptions:
dest:
description:
- Destination of the logs.
choices: ['on', 'host', 'console', 'buffered', 'persistence', 'rfc5424']
type: str
name:
description:
- ipv4 address/ipv6 address/name of syslog server.
type: str
udp_port:
description:
- UDP port of destination host(syslog server).
type: str
facility:
description:
- Specifies log facility to log messages from the device.
choices: ['auth','cron','daemon','kern','local0', 'local1', 'local2', 'local3', 'local4', 'local5', 'local6', 'local7', 'user',
'lpr','mail','news','syslog','sys9','sys10','sys11','sys12','sys13','sys14','user','uucp']
type: str
level:
description:
- Specifies the message level.
type: list
choices: ['alerts', 'critical', 'debugging', 'emergencies', 'errors', 'informational',
'notifications', 'warnings']
state:
description:
- State of the logging configuration.
choices: ['present', 'absent']
type: str
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
state:
description:
- State of the logging configuration.
default: present
choices: ['present', 'absent']
type: str
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 host logging.
icx_logging:
dest: host
name: 172.16.0.1
udp_port: 5555
- name: Remove host logging configuration.
icx_logging:
dest: host
name: 172.16.0.1
udp_port: 5555
state: absent
- name: Disables the real-time display of syslog messages.
icx_logging:
dest: console
state: absent
- name: Enables local syslog logging.
icx_logging:
dest : on
state: present
- name: configure buffer level.
icx_logging:
dest: buffered
level: critical
- name: Configure logging using aggregate
icx_logging:
aggregate:
- { dest: buffered, level: ['notifications','errors'] }
- name: remove logging using aggregate
icx_logging:
aggregate:
- { dest: console }
- { dest: host, name: 172.16.0.1, udp_port: 5555 }
state: absent
"""
RETURN = """
commands:
description: The list of configuration mode commands to send to the device
returned: always
type: list
sample:
- logging host 172.16.0.1
- logging console
"""
import re
from copy import deepcopy
from ansible.module_utils.basic import AnsibleModule, env_fallback
from ansible.module_utils.connection import Connection, ConnectionError, exec_command
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import remove_default_spec, validate_ip_v6_address
from ansible_collections.community.general.plugins.module_utils.network.icx.icx import get_config, load_config
def search_obj_in_list(name, lst):
for o in lst:
if o['name'] == name:
return o
def diff_in_list(want, have):
adds = set()
removes = set()
for w in want:
if w['dest'] == 'buffered':
for h in have:
if h['dest'] == 'buffered':
adds = w['level'] - h['level']
removes = h['level'] - w['level']
return adds, removes
return adds, removes
def map_obj_to_commands(updates):
dest_group = ('host', 'console', 'persistence', 'enable')
commands = list()
want, have = updates
for w in want:
dest = w['dest']
name = w['name']
level = w['level']
state = w['state']
udp_port = w['udp_port']
facility = w['facility']
del w['state']
del w['facility']
facility_name = ''
facility_level = ''
if name is not None and validate_ip_v6_address(name):
name = 'ipv6 ' + name
if facility:
for item in have:
if item['dest'] == 'facility':
facility_name = item['dest']
facility_level = item['facility']
if state == 'absent':
if have == []:
if facility:
commands.append('no logging facility')
if dest == 'buffered':
for item in have:
if item['dest'] == 'buffered':
want_level = level
have_level = item['level']
for item in want_level:
commands.append('no logging buffered {0}'.format(item))
if dest == 'host':
if name and udp_port:
commands.append('no logging host {0} udp-port {1}'.format(name, udp_port))
elif name:
commands.append('no logging host {0}'.format(name))
else:
if dest == 'rfc5424':
commands.append('no logging enable {0}'.format(dest))
else:
if dest != 'buffered':
commands.append('no logging {0}'.format(dest))
if facility:
if facility_name == 'facility' and facility_level != 'user':
commands.append('no logging facility')
if dest == 'buffered':
for item in have:
if item['dest'] == 'buffered':
want_level = level
have_level = item['level']
for item in want_level:
if item in have_level:
commands.append('no logging buffered {0}'.format(item))
if w in have:
if dest == 'host':
if name and udp_port:
commands.append('no logging host {0} udp-port {1}'.format(name, udp_port))
elif name:
commands.append('no logging host {0}'.format(name))
else:
if dest == 'rfc5424':
commands.append('no logging enable {0}'.format(dest))
else:
if dest != 'buffered':
commands.append('no logging {0}'.format(dest))
if state == 'present':
if facility:
if facility != facility_level:
commands.append('logging facility {0}'.format(facility))
if w not in have:
if dest == 'host':
if name and udp_port:
commands.append('logging host {0} udp-port {1}'.format(name, udp_port))
elif name:
commands.append('logging host {0}'.format(name))
elif dest == 'buffered':
adds, removes = diff_in_list(want, have)
for item in adds:
commands.append('logging buffered {0}'.format(item))
for item in removes:
commands.append('no logging buffered {0}'.format(item))
elif dest == 'rfc5424':
commands.append('logging enable {0}'.format(dest))
else:
commands.append('logging {0}'.format(dest))
return commands
def parse_port(line, dest):
port = None
if dest == 'host':
match = re.search(r'logging host \S+\s+udp-port\s+(\d+)', line, re.M)
if match:
port = match.group(1)
else:
match_port = re.search(r'logging host ipv6 \S+\s+udp-port\s+(\d+)', line, re.M)
if match_port:
port = match_port.group(1)
return port
def parse_name(line, dest):
name = None
if dest == 'host':
match = re.search(r'logging host (\S+)', line, re.M)
if match:
if match.group(1) == 'ipv6':
ipv6_add = re.search(r'logging host ipv6 (\S+)', line, re.M)
name = ipv6_add.group(1)
else:
name = match.group(1)
return name
def parse_address(line, dest):
if dest == 'host':
match = re.search(r'^logging host ipv6 (\S+)', line.strip(), re.M)
if match:
return True
return False
def map_config_to_obj(module):
obj = []
facility = ''
addr6 = False
dest_group = ('host', 'console', 'buffered', 'persistence', 'enable')
dest_level = ('alerts', 'critical', 'debugging', 'emergencies', 'errors', 'informational', 'notifications', 'warnings')
buff_level = list()
if module.params['check_running_config'] is False:
return []
data = get_config(module, flags=['| include logging'])
facility_match = re.search(r'^logging facility (\S+)', data, re.M)
if facility_match:
facility = facility_match.group(1)
obj.append({
'dest': 'facility',
'facility': facility
})
else:
obj.append({
'dest': 'facility',
'facility': 'user'
})
for line in data.split('\n'):
match = re.search(r'^logging (\S+)', line.strip(), re.M)
if match:
if match.group(1) in dest_group:
dest = match.group(1)
if dest == 'host':
obj.append({
'dest': dest,
'name': parse_name(line.strip(), dest),
'udp_port': parse_port(line, dest),
'level': None,
'addr6': parse_address(line, dest)
})
elif dest == 'buffered':
obj.append({
'dest': dest,
'level': None,
'name': None,
'udp_port': None,
'addr6': False
})
else:
if dest == 'enable':
dest = 'rfc5424'
obj.append({
'dest': dest,
'level': None,
'name': None,
'udp_port': None,
'addr6': False
})
else:
ip_match = re.search(r'^no logging buffered (\S+)', line, re.M)
if ip_match:
dest = 'buffered'
buff_level.append(ip_match.group(1))
if 'no logging on' not in data:
obj.append({
'dest': 'on',
'level': None,
'name': None,
'udp_port': None,
'addr6': False
})
levels = set()
for items in dest_level:
if items not in buff_level:
levels.add(items)
obj.append({
'dest': 'buffered',
'level': levels,
'name': None,
'udp_port': None,
'addr6': False
})
return obj
def count_terms(check, param=None):
count = 0
for term in check:
if param[term] is not None:
count += 1
return count
def check_required_if(module, spec, param):
for sp in spec:
missing = []
max_missing_count = 0
is_one_of = False
if len(sp) == 4:
key, val, requirements, is_one_of = sp
else:
key, val, requirements = sp
if is_one_of:
max_missing_count = len(requirements)
term = 'any'
else:
term = 'all'
if key in param and param[key] == val:
for check in requirements:
count = count_terms((check,), param)
if count == 0:
missing.append(check)
if len(missing) and len(missing) >= max_missing_count:
msg = "%s is %s but %s of the following are missing: %s" % (key, val, term, ', '.join(missing))
module.fail_json(msg=msg)
def map_params_to_obj(module, required_if=None):
obj = []
addr6 = False
aggregate = module.params.get('aggregate')
if aggregate:
for item in aggregate:
if item['name'] is not None and validate_ip_v6_address(item['name']):
addr6 = True
for key in item:
if item.get(key) is None:
item[key] = module.params[key]
check_required_if(module, required_if, item)
item.update({'addr6': addr6})
d = item.copy()
d['level'] = set(d['level']) if d['level'] is not None else None
if d['dest'] != 'host':
d['name'] = None
d['udp_port'] = None
if d['dest'] != 'buffered':
d['level'] = None
del d['check_running_config']
obj.append(d)
else:
if module.params['name'] is not None and validate_ip_v6_address(module.params['name']):
addr6 = True
if module.params['dest'] != 'host':
module.params['name'] = None
module.params['udp_port'] = None
if module.params['dest'] != 'buffered':
module.params['level'] = None
obj.append({
'dest': module.params['dest'],
'name': module.params['name'],
'udp_port': module.params['udp_port'],
'level': set(module.params['level']) if module.params['level'] else None,
'facility': module.params['facility'],
'state': module.params['state'],
'addr6': addr6
})
return obj
def main():
""" main entry point for module execution
"""
element_spec = dict(
dest=dict(
type='str',
choices=[
'on',
'host',
'console',
'buffered',
'persistence',
'rfc5424']),
name=dict(
type='str'),
udp_port=dict(),
level=dict(
type='list',
choices=[
'alerts',
'critical',
'debugging',
'emergencies',
'errors',
'informational',
'notifications',
'warnings']),
facility=dict(
type='str',
choices=[
'auth',
'cron',
'daemon',
'kern',
'local0',
'local1',
'local2',
'local3',
'local4',
'local5',
'local6',
'local7',
'user',
'lpr',
'mail',
'news',
'syslog',
'sys9',
'sys10',
'sys11',
'sys12',
'sys13',
'sys14',
'user',
'uucp']),
state=dict(
default='present',
choices=[
'present',
'absent']),
check_running_config=dict(default=True, type='bool', fallback=(env_fallback, ['ANSIBLE_CHECK_ICX_RUNNING_CONFIG'])))
aggregate_spec = deepcopy(element_spec)
remove_default_spec(aggregate_spec)
argument_spec = dict(
aggregate=dict(type='list', elements='dict', options=aggregate_spec),
)
argument_spec.update(element_spec)
required_if = [('dest', 'host', ['name']),
('dest', 'buffered', ['level'])]
module = AnsibleModule(argument_spec=argument_spec,
required_if=required_if,
supports_check_mode=True)
result = {'changed': False}
warnings = list()
exec_command(module, 'skip')
if warnings:
result['warnings'] = warnings
want = map_params_to_obj(module, required_if=required_if)
have = map_config_to_obj(module)
result['want'] = want
result['have'] = have
commands = map_obj_to_commands((want, have))
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()

View File

@@ -0,0 +1,269 @@
#!/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_ping
author: "Ruckus Wireless (@Commscope)"
short_description: Tests reachability using ping from Ruckus ICX 7000 series switches
description:
- Tests reachability using ping from switch to a remote destination.
notes:
- Tested against ICX 10.1
options:
count:
description:
- Number of packets to send. Default is 1.
type: int
dest:
description:
- ip-addr | host-name | vrf vrf-name | ipv6 [ ipv6-addr | host-name | vrf vrf-name] (resolvable by switch) of the remote node.
required: true
type: str
timeout:
description:
- Specifies the time, in milliseconds for which the device waits for a reply from the pinged device.
The value can range from 1 to 4294967296. The default is 5000 (5 seconds).
type: int
ttl:
description:
- Specifies the time to live as a maximum number of hops. The value can range from 1 to 255. The default is 64.
type: int
size:
description:
- Specifies the size of the ICMP data portion of the packet, in bytes. This is the payload and does not include the header.
The value can range from 0 to 10000. The default is 16..
type: int
source:
description:
- IP address to be used as the origin of the ping packets.
type: str
vrf:
description:
- Specifies the Virtual Routing and Forwarding (VRF) instance of the device to be pinged.
type: str
state:
description:
- Determines if the expected result is success or fail.
type: str
choices: [ absent, present ]
default: present
'''
EXAMPLES = r'''
- name: Test reachability to 10.10.10.10
icx_ping:
dest: 10.10.10.10
- name: Test reachability to ipv6 address from source with timeout
icx_ping:
dest: ipv6 2001:cdba:0000:0000:0000:0000:3257:9652
source: 10.1.1.1
timeout: 100000
- name: Test reachability to 10.1.1.1 through vrf using 5 packets
icx_ping:
dest: 10.1.1.1
vrf: x.x.x.x
count: 5
- name: Test unreachability to 10.30.30.30
icx_ping:
dest: 10.40.40.40
state: absent
- name: Test reachability to ipv4 with ttl and packet size
icx_ping:
dest: 10.10.10.10
ttl: 20
size: 500
'''
RETURN = '''
commands:
description: Show the command sent.
returned: always
type: list
sample: ["ping 10.40.40.40 count 20 source loopback0", "ping 10.40.40.40"]
packet_loss:
description: Percentage of packets lost.
returned: always
type: str
sample: "0%"
packets_rx:
description: Packets successfully received.
returned: always
type: int
sample: 20
packets_tx:
description: Packets successfully transmitted.
returned: always
type: int
sample: 20
rtt:
description: Show RTT stats.
returned: always
type: dict
sample: {"avg": 2, "max": 8, "min": 1}
'''
from ansible.module_utils._text import to_text
from ansible_collections.community.general.plugins.module_utils.network.icx.icx import run_commands
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.connection import Connection, ConnectionError
import re
def build_ping(dest, count=None, source=None, timeout=None, ttl=None, size=None, vrf=None):
"""
Function to build the command to send to the terminal for the switch
to execute. All args come from the module's unique params.
"""
if vrf is not None:
cmd = "ping vrf {0} {1}".format(vrf, dest)
else:
cmd = "ping {0}".format(dest)
if count is not None:
cmd += " count {0}".format(str(count))
if timeout is not None:
cmd += " timeout {0}".format(str(timeout))
if ttl is not None:
cmd += " ttl {0}".format(str(ttl))
if size is not None:
cmd += " size {0}".format(str(size))
if source is not None:
cmd += " source {0}".format(source)
return cmd
def parse_ping(ping_stats):
"""
Function used to parse the statistical information from the ping response.
Example: "Success rate is 100 percent (5/5), round-trip min/avg/max=40/51/55 ms."
Returns the percent of packet loss, received packets, transmitted packets, and RTT dict.
"""
if ping_stats.startswith('Success'):
rate_re = re.compile(r"^\w+\s+\w+\s+\w+\s+(?P<pct>\d+)\s+\w+\s+\((?P<rx>\d+)/(?P<tx>\d+)\)")
rtt_re = re.compile(r".*,\s+\S+\s+\S+=(?P<min>\d+)/(?P<avg>\d+)/(?P<max>\d+)\s+\w+\.+\s*$|.*\s*$")
rate = rate_re.match(ping_stats)
rtt = rtt_re.match(ping_stats)
return rate.group("pct"), rate.group("rx"), rate.group("tx"), rtt.groupdict()
else:
rate_re = re.compile(r"^Sending+\s+(?P<tx>\d+),")
rate = rate_re.match(ping_stats)
rtt = {'avg': 0, 'max': 0, 'min': 0}
return 0, 0, rate.group('tx'), rtt
def validate_results(module, loss, results):
"""
This function is used to validate whether the ping results were unexpected per "state" param.
"""
state = module.params["state"]
if state == "present" and loss == 100:
module.fail_json(msg="Ping failed unexpectedly", **results)
elif state == "absent" and loss < 100:
module.fail_json(msg="Ping succeeded unexpectedly", **results)
def validate_fail(module, responses):
if ("Success" in responses or "No reply" in responses) is False:
module.fail_json(msg=responses)
def validate_parameters(module, timeout, count):
if timeout and not 1 <= int(timeout) <= 4294967294:
module.fail_json(msg="bad value for timeout - valid range (1-4294967294)")
if count and not 1 <= int(count) <= 4294967294:
module.fail_json(msg="bad value for count - valid range (1-4294967294)")
def main():
""" main entry point for module execution
"""
argument_spec = dict(
count=dict(type="int"),
dest=dict(type="str", required=True),
timeout=dict(type="int"),
ttl=dict(type="int"),
size=dict(type="int"),
source=dict(type="str"),
state=dict(type="str", choices=["absent", "present"], default="present"),
vrf=dict(type="str")
)
module = AnsibleModule(argument_spec=argument_spec)
count = module.params["count"]
dest = module.params["dest"]
source = module.params["source"]
timeout = module.params["timeout"]
ttl = module.params["ttl"]
size = module.params["size"]
vrf = module.params["vrf"]
results = {}
warnings = list()
if warnings:
results["warnings"] = warnings
response = ''
try:
validate_parameters(module, timeout, count)
results["commands"] = [build_ping(dest, count, source, timeout, ttl, size, vrf)]
ping_results = run_commands(module, commands=results["commands"])
ping_results_list = ping_results[0].split("\n")
except ConnectionError as exc:
module.fail_json(msg=to_text(exc, errors='surrogate_then_replace'))
validate_fail(module, ping_results[0])
stats = ""
statserror = ''
for line in ping_results_list:
if line.startswith('Sending'):
statserror = line
if line.startswith('Success'):
stats = line
elif line.startswith('No reply'):
stats = statserror
success, rx, tx, rtt = parse_ping(stats)
loss = abs(100 - int(success))
results["packet_loss"] = str(loss) + "%"
results["packets_rx"] = int(rx)
results["packets_tx"] = int(tx)
# Convert rtt values to int
for k, v in rtt.items():
if rtt[k] is not None:
rtt[k] = int(v)
results["rtt"] = rtt
validate_results(module, loss, results)
module.exit_json(**results)
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,314 @@
#!/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_static_route
author: "Ruckus Wireless (@Commscope)"
short_description: Manage static IP routes on Ruckus ICX 7000 series switches
description:
- This module provides declarative management of static
IP routes on Ruckus ICX network devices.
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:
prefix:
description:
- Network prefix of the static route.
type: str
mask:
description:
- Network prefix mask of the static route.
type: str
next_hop:
description:
- Next hop IP of the static route.
type: str
admin_distance:
description:
- Admin distance of the static route. Range is 1 to 255.
type: int
aggregate:
description: List of static route definitions.
type: list
suboptions:
prefix:
description:
- Network prefix of the static route.
type: str
mask:
description:
- Network prefix mask of the static route.
type: str
next_hop:
description:
- Next hop IP of the static route.
type: str
admin_distance:
description:
- Admin distance of the static route. Range is 1 to 255.
type: int
state:
description:
- State of the static route configuration.
type: str
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
purge:
description:
- Purge routes not defined in the I(aggregate) parameter.
default: no
type: bool
state:
description:
- State of the static route 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 static route
icx_static_route:
prefix: 192.168.2.0/24
next_hop: 10.0.0.1
- name: remove configuration
icx_static_route:
prefix: 192.168.2.0
mask: 255.255.255.0
next_hop: 10.0.0.1
state: absent
- name: Add static route aggregates
icx_static_route:
aggregate:
- { prefix: 172.16.32.0, mask: 255.255.255.0, next_hop: 10.0.0.8 }
- { prefix: 172.16.33.0, mask: 255.255.255.0, next_hop: 10.0.0.8 }
- name: remove static route aggregates
icx_static_route:
aggregate:
- { prefix: 172.16.32.0, mask: 255.255.255.0, next_hop: 10.0.0.8 }
- { prefix: 172.16.33.0, mask: 255.255.255.0, next_hop: 10.0.0.8 }
state: absent
"""
RETURN = """
commands:
description: The list of configuration mode commands to send to the device
returned: always
type: list
sample:
- ip route 192.168.2.0 255.255.255.0 10.0.0.1
"""
from copy import deepcopy
import re
from ansible.module_utils._text import to_text
from ansible.module_utils.basic import AnsibleModule, env_fallback
from ansible.module_utils.connection import ConnectionError
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import remove_default_spec
from ansible_collections.community.general.plugins.module_utils.network.icx.icx import get_config, load_config
try:
from ipaddress import ip_network
HAS_IPADDRESS = True
except ImportError:
HAS_IPADDRESS = False
def map_obj_to_commands(want, have, module):
commands = list()
purge = module.params['purge']
for w in want:
for h in have:
for key in ['prefix', 'mask', 'next_hop']:
if w[key] != h[key]:
break
else:
break
else:
h = None
prefix = w['prefix']
mask = w['mask']
next_hop = w['next_hop']
admin_distance = w.get('admin_distance')
if not admin_distance and h:
w['admin_distance'] = admin_distance = h['admin_distance']
state = w['state']
del w['state']
if state == 'absent' and have == []:
commands.append('no ip route %s %s %s' % (prefix, mask, next_hop))
if state == 'absent' and w in have:
commands.append('no ip route %s %s %s' % (prefix, mask, next_hop))
elif state == 'present' and w not in have:
if admin_distance:
commands.append('ip route %s %s %s distance %s' % (prefix, mask, next_hop, admin_distance))
else:
commands.append('ip route %s %s %s' % (prefix, mask, next_hop))
if purge:
commands = []
for h in have:
if h not in want:
commands.append('no ip route %s %s %s' % (prefix, mask, next_hop))
return commands
def map_config_to_obj(module):
obj = []
compare = module.params['check_running_config']
out = get_config(module, flags='| include ip route', compare=compare)
for line in out.splitlines():
splitted_line = line.split()
if len(splitted_line) not in (4, 5, 6):
continue
cidr = ip_network(to_text(splitted_line[2]))
prefix = str(cidr.network_address)
mask = str(cidr.netmask)
next_hop = splitted_line[3]
if len(splitted_line) == 6:
admin_distance = splitted_line[5]
else:
admin_distance = '1'
obj.append({
'prefix': prefix, 'mask': mask, 'next_hop': next_hop,
'admin_distance': admin_distance
})
return obj
def prefix_length_parser(prefix, mask, module):
if '/' in prefix and mask is not None:
module.fail_json(msg='Ambigous, specifed both length and mask')
if '/' in prefix:
cidr = ip_network(to_text(prefix))
prefix = str(cidr.network_address)
mask = str(cidr.netmask)
return prefix, mask
def map_params_to_obj(module, required_together=None):
keys = ['prefix', 'mask', 'next_hop', 'admin_distance', 'state']
obj = []
aggregate = module.params.get('aggregate')
if aggregate:
for item in aggregate:
route = item.copy()
for key in keys:
if route.get(key) is None:
route[key] = module.params.get(key)
module._check_required_together(required_together, route)
prefix, mask = prefix_length_parser(route['prefix'], route['mask'], module)
route.update({'prefix': prefix, 'mask': mask})
obj.append(route)
else:
module._check_required_together(required_together, module.params)
prefix, mask = prefix_length_parser(module.params['prefix'], module.params['mask'], module)
obj.append({
'prefix': prefix,
'mask': mask,
'next_hop': module.params['next_hop'].strip(),
'admin_distance': module.params.get('admin_distance'),
'state': module.params['state'],
})
for route in obj:
if route['admin_distance']:
route['admin_distance'] = str(route['admin_distance'])
return obj
def main():
""" main entry point for module execution
"""
element_spec = dict(
prefix=dict(type='str'),
mask=dict(type='str'),
next_hop=dict(type='str'),
admin_distance=dict(type='int'),
state=dict(default='present', choices=['present', 'absent']),
check_running_config=dict(default=True, type='bool', fallback=(env_fallback, ['ANSIBLE_CHECK_ICX_RUNNING_CONFIG']))
)
aggregate_spec = deepcopy(element_spec)
aggregate_spec['prefix'] = dict(required=True)
remove_default_spec(aggregate_spec)
argument_spec = dict(
aggregate=dict(type='list', elements='dict', options=aggregate_spec),
purge=dict(default=False, type='bool')
)
argument_spec.update(element_spec)
required_one_of = [['aggregate', 'prefix']]
required_together = [['prefix', 'next_hop']]
mutually_exclusive = [['aggregate', 'prefix']]
module = AnsibleModule(argument_spec=argument_spec,
required_one_of=required_one_of,
mutually_exclusive=mutually_exclusive,
supports_check_mode=True)
if not HAS_IPADDRESS:
module.fail_json(msg="ipaddress python package is required")
warnings = list()
result = {'changed': False}
if warnings:
result['warnings'] = warnings
want = map_params_to_obj(module, required_together=required_together)
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()

View File

@@ -0,0 +1,470 @@
#!/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()

View File

@@ -0,0 +1,390 @@
#!/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_user
author: "Ruckus Wireless (@Commscope)"
short_description: Manage the user accounts on Ruckus ICX 7000 series switches.
description:
- This module creates or updates user account on network devices. It allows playbooks to manage
either individual usernames or the aggregate of usernames in the
current running config. It also supports purging usernames from the
configuration that are not explicitly defined.
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:
aggregate:
description:
- The set of username objects to be configured on the remote
ICX device. The list entries can either be the username
or a hash of username and properties. This argument is mutually
exclusive with the C(name) argument.
aliases: ['users', 'collection']
type: list
suboptions:
name:
description:
- The username to be configured on the ICX device.
required: true
type: str
configured_password:
description: The password to be configured on the ICX device.
type: str
update_password:
description:
- This argument will instruct the module when to change the password. When
set to C(always), the password will always be updated in the device
and when set to C(on_create) the password will be updated only if
the username is created.
choices: ['on_create', 'always']
type: str
privilege:
description:
- The privilege level to be granted to the user
choices: ['0', '4', '5']
type: str
nopassword:
description:
- Defines the username without assigning
a password. This will allow the user to login to the system
without being authenticated by a password.
type: bool
state:
description:
- Configures the state of the username definition
as it relates to the device operational configuration. When set
to I(present), the username(s) should be configured in the device active
configuration and when set to I(absent) the username(s) should not be
in the device active configuration
choices: ['present', 'absent']
type: str
access_time:
description:
- This parameter indicates the time the file's access time should be set to.
Should be preserve when no modification is required, YYYYMMDDHHMM.SS when using default time format, or now.
Default is None meaning that preserve is the default for state=[file,directory,link,hard] and now is default for state=touch
type: str
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
name:
description:
- The username to be configured on the ICX device.
required: true
type: str
configured_password:
description: The password to be configured on the ICX device.
type: str
update_password:
description:
- This argument will instruct the module when to change the password. When
set to C(always), the password will always be updated in the device
and when set to C(on_create) the password will be updated only if
the username is created.
default: always
choices: ['on_create', 'always']
type: str
privilege:
description:
- The privilege level to be granted to the user
default: 0
choices: ['0', '4', '5']
type: str
nopassword:
description:
- Defines the username without assigning
a password. This will allow the user to login to the system
without being authenticated by a password.
type: bool
purge:
description:
- If set to true module will remove any previously
configured usernames on the device except the current defined set of users.
type: bool
default: false
state:
description:
- Configures the state of the username definition
as it relates to the device operational configuration. When set
to I(present), the username(s) should be configured in the device active
configuration and when set to I(absent) the username(s) should not be
in the device active configuration
default: present
choices: ['present', 'absent']
type: str
access_time:
description:
- This parameter indicates the time the file's access time should be set to.
Should be preserve when no modification is required, YYYYMMDDHHMM.SS when using default time format, or now.
Default is None meaning that preserve is the default for state=[file,directory,link,hard] and now is default for state=touch
type: str
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: create a new user without password
icx_user:
name: user1
nopassword: true
- name: create a new user with password
icx_user:
name: user1
configured_password: 'newpassword'
- name: remove users
icx_user:
name: user1
state: absent
- name: set user privilege level to 5
icx_user:
name: user1
privilege: 5
"""
RETURN = """
commands:
description: The list of configuration mode commands to send to the device
returned: always
type: list
sample:
- username ansible nopassword
- username ansible password-string alethea123
- no username ansible
- username ansible privilege 5
- username ansible enable
"""
from copy import deepcopy
import re
import base64
import hashlib
from functools import partial
from ansible.module_utils.basic import AnsibleModule, env_fallback
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import remove_default_spec
from ansible.module_utils.connection import exec_command
from ansible.module_utils.six import iteritems
from ansible_collections.community.general.plugins.module_utils.network.icx.icx import get_config, load_config
def get_param_value(key, item, module):
if not item.get(key):
value = module.params[key]
else:
value_type = module.argument_spec[key].get('type', 'str')
type_checker = module._CHECK_ARGUMENT_TYPES_DISPATCHER[value_type]
type_checker(item[key])
value = item[key]
validator = globals().get('validate_%s' % key)
if all((value, validator)):
validator(value, module)
return value
def map_params_to_obj(module):
users = module.params['aggregate']
if not users:
if not module.params['name'] and module.params['purge']:
return list()
elif not module.params['name']:
module.fail_json(msg='username is required')
else:
aggregate = [{'name': module.params['name']}]
else:
aggregate = list()
for item in users:
if not isinstance(item, dict):
aggregate.append({'name': item})
elif 'name' not in item:
module.fail_json(msg='name is required')
else:
aggregate.append(item)
objects = list()
for item in aggregate:
get_value = partial(get_param_value, item=item, module=module)
item['configured_password'] = get_value('configured_password')
item['nopassword'] = get_value('nopassword')
item['privilege'] = get_value('privilege')
item['state'] = get_value('state')
objects.append(item)
return objects
def parse_privilege(data):
match = re.search(r'privilege (\S)', data, re.M)
if match:
return match.group(1)
def map_config_to_obj(module):
compare = module.params['check_running_config']
data = get_config(module, flags=['| include username'], compare=compare)
match = re.findall(r'(?:^(?:u|\s{2}u))sername (\S+)', data, re.M)
if not match:
return list()
instances = list()
for user in set(match):
regex = r'username %s .+$' % user
cfg = re.findall(regex, data, re.M)
cfg = '\n'.join(cfg)
obj = {
'name': user,
'state': 'present',
'nopassword': 'nopassword' in cfg,
'configured_password': None,
'privilege': parse_privilege(cfg)
}
instances.append(obj)
return instances
def map_obj_to_commands(updates, module):
commands = list()
state = module.params['state']
update_password = module.params['update_password']
def needs_update(want, have, x):
return want.get(x) and (want.get(x) != have.get(x))
def add(command, want, x):
command.append('username %s %s' % (want['name'], x))
for update in updates:
want, have = update
if want['state'] == 'absent':
commands.append(user_del_cmd(want['name']))
if needs_update(want, have, 'privilege'):
add(commands, want, 'privilege %s password %s' % (want['privilege'], want['configured_password']))
else:
if needs_update(want, have, 'configured_password'):
if update_password == 'always' or not have:
add(commands, want, '%spassword %s' % ('privilege ' + str(have.get('privilege')) +
" " if have.get('privilege') is not None else '', want['configured_password']))
if needs_update(want, have, 'nopassword'):
if want['nopassword']:
add(commands, want, 'nopassword')
if needs_update(want, have, 'access_time'):
add(commands, want, 'access-time %s' % want['access_time'])
if needs_update(want, have, 'expiry_days'):
add(commands, want, 'expires %s' % want['expiry_days'])
return commands
def update_objects(want, have):
updates = list()
for entry in want:
item = next((i for i in have if i['name'] == entry['name']), None)
if all((item is None, entry['state'] == 'present')):
updates.append((entry, {}))
elif all((have == [], entry['state'] == 'absent')):
for key, value in iteritems(entry):
if key not in ['update_password']:
updates.append((entry, item))
break
elif item:
for key, value in iteritems(entry):
if key not in ['update_password']:
if value is not None and value != item.get(key):
updates.append((entry, item))
break
return updates
def user_del_cmd(username):
return 'no username %s' % username
def main():
"""entry point for module execution
"""
element_spec = dict(
name=dict(),
configured_password=dict(no_log=True),
nopassword=dict(type='bool', default=False),
update_password=dict(default='always', choices=['on_create', 'always']),
privilege=dict(type='str', choices=['0', '4', '5']),
access_time=dict(type='str'),
state=dict(default='present', choices=['present', 'absent']),
check_running_config=dict(default=True, type='bool', fallback=(env_fallback, ['ANSIBLE_CHECK_ICX_RUNNING_CONFIG']))
)
aggregate_spec = deepcopy(element_spec)
aggregate_spec['name'] = dict(required=True)
remove_default_spec(aggregate_spec)
argument_spec = dict(
aggregate=dict(type='list', elements='dict', options=aggregate_spec, aliases=['users', 'collection']),
purge=dict(type='bool', default=False)
)
argument_spec.update(element_spec)
mutually_exclusive = [('name', 'aggregate')]
module = AnsibleModule(argument_spec=argument_spec,
mutually_exclusive=mutually_exclusive,
supports_check_mode=True)
result = {'changed': False}
exec_command(module, 'skip')
want = map_params_to_obj(module)
have = map_config_to_obj(module)
commands = map_obj_to_commands(update_objects(want, have), module)
if module.params['purge']:
want_users = [x['name'] for x in want]
have_users = [x['name'] for x in have]
for item in set(have_users).difference(want_users):
if item != 'admin':
commands.append(user_del_cmd(item))
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()

View File

@@ -0,0 +1,783 @@
#!/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_vlan
author: "Ruckus Wireless (@Commscope)"
short_description: Manage VLANs on Ruckus ICX 7000 series switches
description:
- This module provides declarative management of VLANs
on ICX network devices.
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:
name:
description:
- Name of the VLAN.
type: str
vlan_id:
description:
- ID of the VLAN. Range 1-4094.
required: true
type: int
interfaces:
description:
- List of ethernet ports or LAGS to be added as access(untagged) ports to the vlan.
To add a range of ports use 'to' keyword. See the example.
suboptions:
name:
description:
- Name of the interface or lag
type: list
purge:
description:
- Purge interfaces not defined in the I(name)
type: bool
type: dict
tagged:
description:
- List of ethernet ports or LAGS to be added as trunk(tagged) ports to the vlan.
To add a range of ports use 'to' keyword. See the example.
suboptions:
name:
description:
- Name of the interface or lag
type: list
purge:
description:
- Purge interfaces not defined in the I(name)
type: bool
type: dict
ip_dhcp_snooping:
description:
- Enables DHCP snooping on a VLAN.
type: bool
ip_arp_inspection:
description:
- Enables dynamic ARP inspection on a VLAN.
type: bool
associated_interfaces:
description:
- This is a intent option and checks the operational state of the for given vlan C(name)
for associated interfaces. If the value in the C(associated_interfaces) does not match with
the operational state of vlan interfaces on device it will result in failure.
type: list
associated_tagged:
description:
- This is a intent option and checks the operational state of given vlan C(name)
for associated tagged ports and lags. If the value in the C(associated_tagged) does not match with
the operational state of vlan interfaces on device it will result in failure.
type: list
delay:
description:
- Delay the play should wait to check for declarative intent params values.
default: 10
type: int
stp:
description:
- Enable spanning-tree 802-1w/rstp for this vlan.
suboptions:
type:
description:
- Specify the type of spanning-tree
type: str
default: 802-1w
choices: ['802-1w','rstp']
priority:
description:
- Configures the priority of the bridge. The value ranges from
0 through 65535. A lower numerical value means the bridge has
a higher priority. Thus, the highest priority is 0. The default is 32768.
type: str
enabled:
description:
- Manage the state(Enable/Disable) of the spanning_tree_802_1w in the current vlan
type: bool
type: dict
aggregate:
description:
- List of VLANs definitions.
type: list
suboptions:
name:
description:
- Name of the VLAN.
type: str
vlan_id:
description:
- ID of the VLAN. Range 1-4094.
required: true
type: str
ip_dhcp_snooping:
description:
- Enables DHCP snooping on a VLAN.
type: bool
ip_arp_inspection:
description:
- Enables dynamic ARP inspection on a VLAN.
type: bool
tagged:
description:
- List of ethernet ports or LAGS to be added as trunk(tagged) ports to the vlan.
To add a range of ports use 'to' keyword. See the example.
suboptions:
name:
description:
- Name of the interface or lag
type: list
purge:
description:
- Purge interfaces not defined in the I(name)
type: bool
type: dict
interfaces:
description:
- List of ethernet ports or LAGS to be added as access(untagged) ports to the vlan.
To add a range of ports use 'to' keyword. See the example.
suboptions:
name:
description:
- Name of the interface or lag
type: list
purge:
description:
- Purge interfaces not defined in the I(name)
type: bool
type: dict
delay:
description:
- Delay the play should wait to check for declarative intent params values.
type: int
stp:
description:
- Enable spanning-tree 802-1w/rstp for this vlan.
suboptions:
type:
description:
- Specify the type of spanning-tree
type: str
default: 802-1w
choices: ['802-1w','rstp']
priority:
description:
- Configures the priority of the bridge. The value ranges from
0 through 65535. A lower numerical value means the bridge has
a higher priority. Thus, the highest priority is 0. The default is 32768.
type: str
enabled:
description:
- Manage the state(Enable/Disable) of the spanning_tree_802_1w in the current vlan
type: bool
type: dict
state:
description:
- State of the VLAN configuration.
type: str
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
associated_interfaces:
description:
- This is a intent option and checks the operational state of the for given vlan C(name)
for associated interfaces. If the value in the C(associated_interfaces) does not match with
the operational state of vlan interfaces on device it will result in failure.
type: list
associated_tagged:
description:
- This is a intent option and checks the operational state of given vlan C(name)
for associated tagged ports and lags. If the value in the C(associated_tagged) does not match with
the operational state of vlan interfaces on device it will result in failure.
type: list
purge:
description:
- Purge VLANs not defined in the I(aggregate) parameter.
default: no
type: bool
state:
description:
- State of the VLAN 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: Add a single ethernet 1/1/48 as access(untagged) port to vlan 20
icx_vlan:
name: test-vlan
vlan_id: 20
interfaces:
name:
- ethernet 1/1/48
- name: Add a single LAG 10 as access(untagged) port to vlan 20
icx_vlan:
vlan_id: 20
interfaces:
name:
- lag 10
- name: Add a range of ethernet ports as trunk(tagged) ports to vlan 20 by port
icx_vlan:
vlan_id: 20
tagged:
name:
- ethernet 1/1/40 to 1/1/48
- name: Add discontinuous lags, ethernet ports as access(untagged) and trunk(tagged) port to vlan 20.
icx_vlan:
vlan_id: 20
interfaces:
name:
- ethernet 1/1/40 to 1/1/48
- ethernet 2/1/1
- lag 1
- lag 3 to 5
tagged:
name:
- ethernet 1/1/20 to 1/1/25
- lag 1 to 3
- name: Remove an access and range of trunk ports from vlan
icx_vlan:
vlan_id: 20
interfaces:
name:
- ethernet 1/1/40
tagged:
name:
- ethernet 1/1/39 to 1/1/70
- name: Enable dhcp snooping, disable arp inspection in vlan
icx_vlan:
vlan_id: 20
ip_dhcp_snooping: present
ip_arp_inspection: absent
- name: Create vlan 20. Enable arp inspection in vlan. Purge all other vlans.
icx_vlan:
vlan_id: 20
ip_arp_inspection: present
purge: present
- name: Remove vlan 20.
icx_vlan:
vlan_id: 20
state: absent
"""
RETURN = """
commands:
description: The list of configuration mode commands to send to the device
returned: always
type: list
sample:
- vlan 100
- name test-vlan
"""
import re
from time import sleep
import itertools
from copy import deepcopy
from time import sleep
from ansible.module_utils._text import to_text
from ansible.module_utils.basic import AnsibleModule, env_fallback
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.config import NetworkConfig
from ansible_collections.community.general.plugins.module_utils.network.icx.icx import load_config, get_config
from ansible.module_utils.connection import Connection, ConnectionError, exec_command
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import conditional, remove_default_spec
def search_obj_in_list(vlan_id, lst):
obj = list()
for o in lst:
if str(o['vlan_id']) == vlan_id:
return o
def parse_vlan_brief(module, vlan_id):
command = 'show run vlan %s' % vlan_id
rc, out, err = exec_command(module, command)
lines = out.split('\n')
untagged_ports = list()
untagged_lags = list()
tagged_ports = list()
tagged_lags = list()
for line in lines:
if 'tagged' in line.split():
lags = line.split(" lag ")
ports = lags[0].split(" ethe ")
del ports[0]
del lags[0]
for port in ports:
if "to" in port:
p = port.split(" to ")
pr = int(p[1].split('/')[2]) - int(p[0].split('/')[2])
for i in range(0, pr + 1):
tagged_ports.append((int(p[0].split('/')[2]) + i))
else:
tagged_ports.append(int(port.split('/')[2]))
for lag in lags:
if "to" in lag:
l = lag.split(" to ")
lr = int(l[1]) - int(l[0])
for i in range(0, lr + 1):
tagged_lags.append((int(l[0]) + i))
else:
tagged_lags.append(int(lag))
if 'untagged' in line.split():
lags = line.split(" lag ")
ports = lags[0].split(" ethe ")
del ports[0]
del lags[0]
for port in ports:
if "to" in port:
p = port.split(" to ")
pr = int(p[1].split('/')[2]) - int(p[0].split('/')[2])
for i in range(0, pr + 1):
untagged_ports.append((int(p[0].split('/')[2]) + i))
else:
untagged_ports.append(int(port.split('/')[2]))
for lag in lags:
if "to" in lag:
l = lag.split(" to ")
lr = int(l[1]) - int(l[0])
for i in range(0, lr + 1):
untagged_lags.append((int(l[0]) + i))
else:
untagged_lags.append(int(lag))
return untagged_ports, untagged_lags, tagged_ports, tagged_lags
def extract_list_from_interface(interface):
if 'ethernet' in interface:
if 'to' in interface:
s = re.search(r"\d+\/\d+/(?P<low>\d+)\sto\s+\d+\/\d+/(?P<high>\d+)", interface)
low = int(s.group('low'))
high = int(s.group('high'))
else:
s = re.search(r"\d+\/\d+/(?P<low>\d+)", interface)
low = int(s.group('low'))
high = int(s.group('low'))
elif 'lag' in interface:
if 'to' in interface:
s = re.search(r"(?P<low>\d+)\sto\s(?P<high>\d+)", interface)
low = int(s.group('low'))
high = int(s.group('high'))
else:
s = re.search(r"(?P<low>\d+)", interface)
low = int(s.group('low'))
high = int(s.group('low'))
return low, high
def parse_vlan_id(module):
vlans = []
command = 'show vlan brief'
rc, out, err = exec_command(module, command)
lines = out.split('\n')
for line in lines:
if 'VLANs Configured :' in line:
values = line.split(':')[1]
vlans = [s for s in values.split() if s.isdigit()]
s = re.findall(r"(?P<low>\d+)\sto\s(?P<high>\d+)", values)
for ranges in s:
low = int(ranges[0]) + 1
high = int(ranges[1])
while(high > low):
vlans.append(str(low))
low = low + 1
return vlans
def spanning_tree(module, stp):
stp_cmd = list()
if stp.get('enabled') is False:
if stp.get('type') == '802-1w':
stp_cmd.append('no spanning-tree' + ' ' + stp.get('type'))
stp_cmd.append('no spanning-tree')
elif stp.get('type'):
stp_cmd.append('spanning-tree' + ' ' + stp.get('type'))
if stp.get('priority') and stp.get('type') == 'rstp':
module.fail_json(msg='spanning-tree 802-1w only can have priority')
elif stp.get('priority'):
stp_cmd.append('spanning-tree' + ' ' + stp.get('type') + ' ' + 'priority' + ' ' + stp.get('priority'))
return stp_cmd
def map_params_to_obj(module):
obj = []
aggregate = module.params.get('aggregate')
if aggregate:
for item in aggregate:
for key in item:
if item.get(key) is None:
item[key] = module.params[key]
stp = item.get('stp')
if stp:
stp_cmd = spanning_tree(module, stp)
item.update({'stp': stp_cmd})
d = item.copy()
obj.append(d)
else:
params = {
'name': module.params['name'],
'vlan_id': module.params['vlan_id'],
'interfaces': module.params['interfaces'],
'tagged': module.params['tagged'],
'associated_interfaces': module.params['associated_interfaces'],
'associated_tagged': module.params['associated_tagged'],
'delay': module.params['delay'],
'ip_dhcp_snooping': module.params['ip_dhcp_snooping'],
'ip_arp_inspection': module.params['ip_arp_inspection'],
'state': module.params['state'],
}
stp = module.params.get('stp')
if stp:
stp_cmd = spanning_tree(module, stp)
params.update({'stp': stp_cmd})
obj.append(params)
return obj
def map_obj_to_commands(updates, module):
commands = list()
want, have = updates
purge = module.params['purge']
for w in want:
vlan_id = w['vlan_id']
state = w['state']
name = w['name']
interfaces = w.get('interfaces')
tagged = w.get('tagged')
dhcp = w.get('ip_dhcp_snooping')
arp = w.get('ip_arp_inspection')
stp = w.get('stp')
obj_in_have = search_obj_in_list(str(vlan_id), have)
if state == 'absent':
if have == []:
commands.append('no vlan {0}'.format(vlan_id))
if obj_in_have:
commands.append('no vlan {0}'.format(vlan_id))
elif state == 'present':
if not obj_in_have:
commands.append('vlan {0}'.format(vlan_id))
if name:
commands.append('vlan {0} name {1}'.format(vlan_id, name))
if interfaces:
if interfaces['name']:
for item in interfaces['name']:
commands.append('untagged {0}'.format(item))
if tagged:
if tagged['name']:
for item in tagged['name']:
commands.append('tagged {0}'.format(item))
if dhcp is True:
commands.append('ip dhcp snooping vlan {0}'.format(vlan_id))
elif dhcp is False:
commands.append('no ip dhcp snooping vlan {0}'.format(vlan_id))
if arp is True:
commands.append('ip arp inspection vlan {0}'.format(vlan_id))
elif dhcp is False:
commands.append('no ip arp inspection vlan {0}'.format(vlan_id))
if stp:
if w.get('stp'):
[commands.append(cmd) for cmd in w['stp']]
else:
commands.append('vlan {0}'.format(vlan_id))
if name:
if name != obj_in_have['name']:
commands.append('vlan {0} name {1}'.format(vlan_id, name))
if interfaces:
if interfaces['name']:
have_interfaces = list()
for interface in interfaces['name']:
low, high = extract_list_from_interface(interface)
while(high >= low):
if 'ethernet' in interface:
have_interfaces.append('ethernet 1/1/{0}'.format(low))
if 'lag' in interface:
have_interfaces.append('lag {0}'.format(low))
low = low + 1
if interfaces['purge'] is True:
remove_interfaces = list(set(obj_in_have['interfaces']) - set(have_interfaces))
for item in remove_interfaces:
commands.append('no untagged {0}'.format(item))
if interfaces['name']:
add_interfaces = list(set(have_interfaces) - set(obj_in_have['interfaces']))
for item in add_interfaces:
commands.append('untagged {0}'.format(item))
if tagged:
if tagged['name']:
have_tagged = list()
for tag in tagged['name']:
low, high = extract_list_from_interface(tag)
while(high >= low):
if 'ethernet' in tag:
have_tagged.append('ethernet 1/1/{0}'.format(low))
if 'lag' in tag:
have_tagged.append('lag {0}'.format(low))
low = low + 1
if tagged['purge'] is True:
remove_tagged = list(set(obj_in_have['tagged']) - set(have_tagged))
for item in remove_tagged:
commands.append('no tagged {0}'.format(item))
if tagged['name']:
add_tagged = list(set(have_tagged) - set(obj_in_have['tagged']))
for item in add_tagged:
commands.append('tagged {0}'.format(item))
if dhcp != obj_in_have['ip_dhcp_snooping']:
if dhcp is True:
commands.append('ip dhcp snooping vlan {0}'.format(vlan_id))
elif dhcp is False:
commands.append('no ip dhcp snooping vlan {0}'.format(vlan_id))
if arp != obj_in_have['ip_arp_inspection']:
if arp is True:
commands.append('ip arp inspection vlan {0}'.format(vlan_id))
elif arp is False:
commands.append('no ip arp inspection vlan {0}'.format(vlan_id))
if stp:
if w.get('stp'):
[commands.append(cmd) for cmd in w['stp']]
if len(commands) == 1 and 'vlan ' + str(vlan_id) in commands:
commands = []
if purge:
commands = []
vlans = parse_vlan_id(module)
for h in vlans:
obj_in_want = search_obj_in_list(h, want)
if not obj_in_want and h != '1':
commands.append('no vlan {0}'.format(h))
return commands
def parse_name_argument(module, item):
command = 'show vlan {0}'.format(item)
rc, out, err = exec_command(module, command)
match = re.search(r"Name (\S+),", out)
if match:
return match.group(1)
def parse_interfaces_argument(module, item, port_type):
untagged_ports, untagged_lags, tagged_ports, tagged_lags = parse_vlan_brief(module, item)
ports = list()
if port_type == "interfaces":
if untagged_ports:
for port in untagged_ports:
ports.append('ethernet 1/1/' + str(port))
if untagged_lags:
for port in untagged_lags:
ports.append('lag ' + str(port))
elif port_type == "tagged":
if tagged_ports:
for port in tagged_ports:
ports.append('ethernet 1/1/' + str(port))
if tagged_lags:
for port in tagged_lags:
ports.append('lag ' + str(port))
return ports
def parse_config_argument(config, arg):
match = re.search(arg, config, re.M)
if match:
return True
else:
return False
def map_config_to_obj(module):
config = get_config(module)
vlans = parse_vlan_id(module)
instance = list()
for item in set(vlans):
obj = {
'vlan_id': item,
'name': parse_name_argument(module, item),
'interfaces': parse_interfaces_argument(module, item, 'interfaces'),
'tagged': parse_interfaces_argument(module, item, 'tagged'),
'ip_dhcp_snooping': parse_config_argument(config, 'ip dhcp snooping vlan {0}'.format(item)),
'ip_arp_inspection': parse_config_argument(config, 'ip arp inspection vlan {0}'.format(item)),
}
instance.append(obj)
return instance
def check_fail(module, output):
error = [
re.compile(r"^error", re.I)
]
for x in output:
for regex in error:
if regex.search(x):
module.fail_json(msg=x)
def check_declarative_intent_params(want, module, result):
def parse_ports(interfaces, ports, lags):
for interface in interfaces:
low, high = extract_list_from_interface(interface)
while(high >= low):
if 'ethernet' in interface:
if not (low in ports):
module.fail_json(msg='One or more conditional statements have not been satisfied ' + interface)
if 'lag' in interface:
if not (low in lags):
module.fail_json(msg='One or more conditional statements have not been satisfied ' + interface)
low = low + 1
is_delay = False
low = 0
high = 0
for w in want:
if w.get('associated_interfaces') is None and w.get('associated_tagged') is None:
continue
if result['changed'] and not is_delay:
sleep(module.params['delay'])
is_delay = True
untagged_ports, untagged_lags, tagged_ports, tagged_lags = parse_vlan_brief(module, w['vlan_id'])
if w['associated_interfaces']:
parse_ports(w.get('associated_interfaces'), untagged_ports, untagged_lags)
if w['associated_tagged']:
parse_ports(w.get('associated_tagged'), tagged_ports, tagged_lags)
def main():
""" main entry point for module execution
"""
stp_spec = dict(
type=dict(default='802-1w', choices=['802-1w', 'rstp']),
priority=dict(),
enabled=dict(type='bool'),
)
inter_spec = dict(
name=dict(type='list'),
purge=dict(type='bool')
)
tagged_spec = dict(
name=dict(type='list'),
purge=dict(type='bool')
)
element_spec = dict(
vlan_id=dict(type='int'),
name=dict(),
interfaces=dict(type='dict', options=inter_spec),
tagged=dict(type='dict', options=tagged_spec),
ip_dhcp_snooping=dict(type='bool'),
ip_arp_inspection=dict(type='bool'),
associated_interfaces=dict(type='list'),
associated_tagged=dict(type='list'),
delay=dict(default=10, type='int'),
stp=dict(type='dict', options=stp_spec),
state=dict(default='present', choices=['present', 'absent']),
check_running_config=dict(default=True, type='bool', fallback=(env_fallback, ['ANSIBLE_CHECK_ICX_RUNNING_CONFIG']))
)
aggregate_spec = deepcopy(element_spec)
aggregate_spec['vlan_id'] = dict(required=True)
remove_default_spec(aggregate_spec)
argument_spec = dict(
aggregate=dict(type='list', elements='dict', options=aggregate_spec),
purge=dict(default=False, type='bool')
)
argument_spec.update(element_spec)
required_one_of = [['vlan_id', 'aggregate']]
mutually_exclusive = [['vlan_id', 'aggregate']]
module = AnsibleModule(argument_spec=argument_spec,
required_one_of=required_one_of,
mutually_exclusive=mutually_exclusive,
supports_check_mode=True)
warnings = list()
result = {}
result['changed'] = False
if warnings:
result['warnings'] = warnings
exec_command(module, 'skip')
want = map_params_to_obj(module)
if module.params['check_running_config'] is False:
have = []
else:
have = map_config_to_obj(module)
commands = map_obj_to_commands((want, have), module)
result['commands'] = commands
if commands:
if not module.check_mode:
output = load_config(module, commands)
if output:
check_fail(module, output)
result['output'] = output
result['changed'] = True
check_declarative_intent_params(want, module, result)
module.exit_json(**result)
if __name__ == '__main__':
main()