1
0
Fork 0
mirror of https://github.com/ansible-collections/community.general.git synced 2026-04-25 05:02:46 +00:00

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,276 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
#
# Copyright (C) 2017 Lenovo, Inc.
#
# This file is part of Ansible
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
#
# Module to Backup Config to Lenovo Switches
# Lenovo Networking
#
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = '''
---
module: cnos_backup
author: "Anil Kumar Muraleedharan (@amuraleedhar)"
short_description: Backup the current running or startup configuration to a
remote server on devices running Lenovo CNOS
description:
- This module allows you to work with switch configurations. It provides a
way to back up the running or startup configurations of a switch to a
remote server. This is achieved by periodically saving a copy of the
startup or running configuration of the network device to a remote server
using FTP, SFTP, TFTP, or SCP. The first step is to create a directory from
where the remote server can be reached. The next step is to provide the
full file path of the location where the configuration will be backed up.
Authentication details required by the remote server must be provided as
well. This module uses SSH to manage network device configuration.
The results of the operation will be placed in a directory named 'results'
that must be created by the user in their local directory to where the
playbook is run.
extends_documentation_fragment:
- community.general.cnos
options:
configType:
description:
- This specifies what type of configuration will be backed up. The
choices are the running or startup configurations. There is no
default value, so it will result in an error if the input is
incorrect.
required: Yes
default: Null
choices: [running-config, startup-config]
protocol:
description:
- This refers to the protocol used by the network device to
interact with the remote server to where to upload the backup
configuration. The choices are FTP, SFTP, TFTP, or SCP. Any other
protocols will result in error. If this parameter is
not specified, there is no default value to be used.
required: Yes
default: Null
choices: [SFTP, SCP, FTP, TFTP]
rcserverip:
description:
-This specifies the IP Address of the remote server to where the
configuration will be backed up.
required: Yes
default: Null
rcpath:
description:
- This specifies the full file path where the configuration file
will be copied on the remote server. In case the relative path is
used as the variable value, the root folder for the user of the
server needs to be specified.
required: Yes
default: Null
serverusername:
description:
- Specify the username for the server relating to the protocol
used.
required: Yes
default: Null
serverpassword:
description:
- Specify the password for the server relating to the protocol
used.
required: Yes
default: Null
'''
EXAMPLES = '''
Tasks : The following are examples of using the module cnos_backup.
These are written in the main.yml file of the tasks directory.
---
- name: Test Running Config Backup
cnos_backup:
deviceType: "{{ hostvars[inventory_hostname]['deviceType'] }}"
outputfile: "./results/test_backup_{{ inventory_hostname }}_output.txt"
configType: running-config
protocol: "sftp"
serverip: "10.241.106.118"
rcpath: "/root/cnos/G8272-running-config.txt"
serverusername: "root"
serverpassword: "root123"
- name: Test Startup Config Backup
cnos_backup:
deviceType: "{{ hostvars[inventory_hostname]['deviceType'] }}"
outputfile: "./results/test_backup_{{ inventory_hostname }}_output.txt"
configType: startup-config
protocol: "sftp"
serverip: "10.241.106.118"
rcpath: "/root/cnos/G8272-startup-config.txt"
serverusername: "root"
serverpassword: "root123"
- name: Test Running Config Backup -TFTP
cnos_backup:
deviceType: "{{ hostvars[inventory_hostname]['deviceType'] }}"
outputfile: "./results/test_backup_{{ inventory_hostname }}_output.txt"
configType: running-config
protocol: "tftp"
serverip: "10.241.106.118"
rcpath: "/anil/G8272-running-config.txt"
serverusername: "root"
serverpassword: "root123"
- name: Test Startup Config Backup - TFTP
cnos_backup:
deviceType: "{{ hostvars[inventory_hostname]['deviceType'] }}"
outputfile: "./results/test_backup_{{ inventory_hostname }}_output.txt"
configType: startup-config
protocol: "tftp"
serverip: "10.241.106.118"
rcpath: "/anil/G8272-startup-config.txt"
serverusername: "root"
serverpassword: "root123"
'''
RETURN = '''
msg:
description: Success or failure message
returned: always
type: str
sample: "Config file transferred to server"
'''
import sys
import time
import socket
import array
import json
import time
import re
import os
try:
from ansible_collections.community.general.plugins.module_utils.network.cnos import cnos
HAS_LIB = True
except Exception:
HAS_LIB = False
from ansible.module_utils.basic import AnsibleModule
from collections import defaultdict
# Utility Method to back up the running config or start up config
# This method supports only SCP or SFTP or FTP or TFTP
# Tuning of timeout parameter is pending
def doConfigBackUp(module, prompt, answer):
host = module.params['host']
server = module.params['serverip']
username = module.params['serverusername']
password = module.params['serverpassword']
protocol = module.params['protocol'].lower()
rcPath = module.params['rcpath']
configType = module.params['configType']
confPath = rcPath + host + '_' + configType + '.txt'
retVal = ''
# config backup command happens here
command = "copy " + configType + " " + protocol + " " + protocol + "://"
command = command + username + "@" + server + "/" + confPath
command = command + " vrf management\n"
cnos.debugOutput(command + "\n")
# cnos.checkForFirstTimeAccess(module, command, 'yes/no', 'yes')
cmd = []
if(protocol == "scp"):
scp_cmd1 = [{'command': command, 'prompt': 'timeout:', 'answer': '0'}]
scp_cmd2 = [{'command': '\n', 'prompt': 'Password:',
'answer': password}]
cmd.extend(scp_cmd1)
cmd.extend(scp_cmd2)
retVal = retVal + str(cnos.run_cnos_commands(module, cmd))
elif(protocol == "sftp"):
sftp_cmd = [{'command': command, 'prompt': 'Password:',
'answer': password}]
cmd.extend(sftp_cmd)
retVal = retVal + str(cnos.run_cnos_commands(module, cmd))
elif(protocol == "ftp"):
ftp_cmd = [{'command': command, 'prompt': 'Password:',
'answer': password}]
cmd.extend(ftp_cmd)
retVal = retVal + str(cnos.run_cnos_commands(module, cmd))
elif(protocol == "tftp"):
command = "copy " + configType + " " + protocol + " " + protocol
command = command + "://" + server + "/" + confPath
command = command + " vrf management\n"
# cnos.debugOutput(command)
tftp_cmd = [{'command': command, 'prompt': None, 'answer': None}]
cmd.extend(tftp_cmd)
retVal = retVal + str(cnos.run_cnos_commands(module, cmd))
else:
return "Error-110"
return retVal
# EOM
def main():
module = AnsibleModule(
argument_spec=dict(
outputfile=dict(required=True),
host=dict(required=False),
username=dict(required=False),
password=dict(required=False, no_log=True),
enablePassword=dict(required=False, no_log=True),
deviceType=dict(required=True),
configType=dict(required=True),
protocol=dict(required=True),
serverip=dict(required=True),
rcpath=dict(required=True),
serverusername=dict(required=False),
serverpassword=dict(required=False, no_log=True),),
supports_check_mode=False)
outputfile = module.params['outputfile']
protocol = module.params['protocol'].lower()
output = ''
if(protocol == "tftp" or protocol == "ftp" or
protocol == "sftp" or protocol == "scp"):
transfer_status = doConfigBackUp(module, None, None)
else:
transfer_status = "Invalid Protocol option"
output = output + "\n Config Back Up status \n" + transfer_status
# Save it into the file
path = outputfile.rsplit('/', 1)
# cnos.debugOutput(path[0])
if not os.path.exists(path[0]):
os.makedirs(path[0])
file = open(outputfile, "a")
file.write(output)
file.close()
# Logic to check when changes occur or not
errorMsg = cnos.checkOutputForError(output)
if(errorMsg is None):
module.exit_json(changed=True, msg="Config file transferred to server")
else:
module.fail_json(msg=errorMsg)
if __name__ == '__main__':
main()

View file

@ -0,0 +1,263 @@
#!/usr/bin/python
#
# Copyright (C) 2017 Lenovo, Inc.
# (c) 2017, Ansible by Red Hat, inc
# This file is part of Ansible
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
#
# Module to send banner commands to Lenovo Switches
# Two types of banners are supported login and motd
# Lenovo Networking
#
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = '''
---
module: cnos_banner
author: "Anil Kumar Muraleedharan (@amuraleedhar)"
short_description: Manage multiline banners on Lenovo CNOS devices
description:
- This will configure both login and motd banners on remote devices
running Lenovo CNOS. It allows playbooks to add or remote
banner text from the active running configuration.
notes:
- Tested against CNOS 10.8.1
options:
banner:
description:
- Specifies which banner should be configured on the remote device.
In Ansible 2.8 and earlier only I(login) and I(motd) were supported.
required: true
choices: ['login', 'motd']
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. Requires
I(state=present).
state:
description:
- Specifies whether or not the configuration is
present in the current devices active running configuration.
default: present
choices: ['present', 'absent']
provider:
description:
- B(Deprecated)
- "Starting with Ansible 2.5 we recommend using
C(connection: network_cli)."
- For more information please see the
L(CNOS Platform Options guide, ../network/user_guide/platform_cnos.html).
- HORIZONTALLINE
- A dict object containing connection details.
suboptions:
host:
description:
- Specifies the DNS host name or address for connecting to the remote
device over the specified transport. The value of host is used as
the destination address for the transport.
required: true
port:
description:
- Specifies the port to use when building the connection to the
remote device.
default: 22
username:
description:
- Configures the username to use to authenticate the connection to
the remote device. This value is used to authenticate
the SSH session. If the value is not specified in the task, the
value of environment variable C(ANSIBLE_NET_USERNAME) will be used
instead.
password:
description:
- Specifies the password to use to authenticate the connection to
the remote device. This value is used to authenticate
the SSH session. If the value is not specified in the task, the
value of environment variable C(ANSIBLE_NET_PASSWORD) will be used
instead.
timeout:
description:
- Specifies the timeout in seconds for communicating with the network
device for either connecting or sending commands. If the timeout
is exceeded before the operation is completed, the module will
error.
default: 10
ssh_keyfile:
description:
- Specifies the SSH key to use to authenticate the connection to
the remote device. This value is the path to the
key used to authenticate the SSH session. If the value is not
specified in the task, the value of environment variable
C(ANSIBLE_NET_SSH_KEYFILE)will be used instead.
authorize:
description:
- Instructs the module to enter privileged mode on the remote device
before sending any commands. If not specified, the device will
attempt to execute all commands in non-privileged mode. If the
value is not specified in the task, the value of environment
variable C(ANSIBLE_NET_AUTHORIZE) will be used instead.
type: bool
default: 'no'
auth_pass:
description:
- Specifies the password to use if required to enter privileged mode
on the remote device. If I(authorize) is false, then this argument
does nothing. If the value is not specified in the task, the value
of environment variable C(ANSIBLE_NET_AUTH_PASS) will be used
instead.
'''
EXAMPLES = """
- name: configure the login banner
cnos_banner:
banner: login
text: |
this is my login banner
that contains a multiline
string
state: present
- name: remove the motd banner
cnos_banner:
banner: motd
state: absent
- name: Configure banner from file
cnos_banner:
banner: motd
text: "{{ lookup('file', './config_partial/raw_banner.cfg') }}"
state: present
"""
RETURN = """
commands:
description: The list of configuration mode commands to send to the device
returned: always
type: list
sample:
- banner login
- this is my login banner
- that contains a multiline
- string
"""
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.connection import exec_command
from ansible_collections.community.general.plugins.module_utils.network.cnos.cnos import load_config, run_commands
from ansible_collections.community.general.plugins.module_utils.network.cnos.cnos import check_args
from ansible_collections.community.general.plugins.module_utils.network.cnos.cnos import cnos_argument_spec
from ansible.module_utils._text import to_text
import re
def map_obj_to_commands(updates, module):
commands = list()
want, have = updates
state = module.params['state']
if state == 'absent' and 'text' in have.keys() and have['text']:
commands.append('no banner %s' % module.params['banner'])
elif state == 'present':
if want['text'] and (want['text'] != have.get('text')):
banner_cmd = 'banner %s ' % module.params['banner']
for bline in want['text'].strip().splitlines():
final_cmd = banner_cmd + bline.strip()
commands.append(final_cmd)
return commands
def map_config_to_obj(module):
rc, out, err = exec_command(module,
'show banner %s' % module.params['banner'])
if rc == 0:
output = out
else:
rc, out, err = exec_command(module,
'show running-config | include banner %s'
% module.params['banner'])
if out:
output = re.search(r'\^C(.*)\^C', out, re.S).group(1).strip()
else:
output = None
obj = {'banner': module.params['banner'], 'state': 'absent'}
if output:
obj['text'] = output
obj['state'] = 'present'
return obj
def map_params_to_obj(module):
text = module.params['text']
if text:
text = to_text(text).strip()
return {
'banner': module.params['banner'],
'text': text,
'state': module.params['state']
}
def main():
""" main entry point for module execution
"""
argument_spec = dict(
banner=dict(required=True, choices=['login', 'motd']),
text=dict(),
state=dict(default='present', choices=['present', 'absent'])
)
argument_spec.update(cnos_argument_spec)
required_if = [('state', 'present', ('text',))]
module = AnsibleModule(argument_spec=argument_spec,
required_if=required_if,
supports_check_mode=True)
warnings = list()
check_args(module, warnings)
result = {'changed': False}
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:
response = load_config(module, commands)
result['changed'] = True
module.exit_json(**result)
if __name__ == '__main__':
main()

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,208 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
#
# (C) 2017 Red Hat Inc.
# Copyright (C) 2017 Lenovo.
#
# GNU General Public License v3.0+
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
#
# Module to execute CNOS Commands on Lenovo Switches.
# Lenovo Networking
#
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = '''
---
module: cnos_command
author: "Anil Kumar Muraleedharan (@amuraleedhar)"
short_description: Run arbitrary commands on Lenovo CNOS devices
description:
- Sends arbitrary commands to an CNOS node and returns the results
read from the device. The C(cnos_command) 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.
options:
commands:
description:
- List of commands to send to the remote device.
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
retires is expired.
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.
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.
default: all
choices: ['any', 'all']
retries:
description:
- Specifies the number of retries 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.
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.
default: 1
'''
EXAMPLES = """
---
- name: test contains operator
cnos_command:
commands:
- show version
- show system memory
wait_for:
- "result[0] contains 'Lenovo'"
- "result[1] contains 'MemFree'"
register: result
- assert:
that:
- "result.changed == false"
- "result.stdout is defined"
- name: get output for single command
cnos_command:
commands: ['show version']
register: result
- assert:
that:
- "result.changed == false"
- "result.stdout is defined"
- name: get output for multiple commands
cnos_command:
commands:
- show version
- show interface information
register: result
- assert:
that:
- "result.changed == false"
- "result.stdout is defined"
- "result.stdout | length == 2"
"""
RETURN = """
stdout:
description: the set of responses from the commands
returned: always
type: list
sample: ['...', '...']
stdout_lines:
description: The value of stdout split into a list
returned: always
type: list
sample: [['...', '...'], ['...'], ['...']]
failed_conditions:
description: the conditionals that failed
returned: failed
type: list
sample: ['...', '...']
"""
import time
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.community.general.plugins.module_utils.network.cnos.cnos import run_commands, check_args
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.parsing import Conditional
from ansible.module_utils.six import string_types
def to_lines(stdout):
for item in stdout:
if isinstance(item, string_types):
item = str(item).split('\n')
yield item
def main():
spec = dict(
# { command: <str>, prompt: <str>, response: <str> }
commands=dict(type='list', required=True),
wait_for=dict(type='list'),
match=dict(default='all', choices=['all', 'any']),
retries=dict(default=10, type='int'),
interval=dict(default=1, type='int')
)
module = AnsibleModule(argument_spec=spec, supports_check_mode=True)
result = {'changed': False}
wait_for = module.params['wait_for'] or list()
conditionals = [Conditional(c) for c in wait_for]
commands = module.params['commands']
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,168 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
#
# Copyright (C) 2017 Lenovo, Inc.
#
# This file is part of Ansible
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
#
# Module to send Conditional CLI commands to Lenovo Switches
# Lenovo Networking
#
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = '''
---
module: cnos_conditional_command
author: "Anil Kumar Muraleedharan (@amuraleedhar)"
short_description: Execute a single command based on condition on devices
running Lenovo CNOS
description:
- This module allows you to modify the running configuration of a switch. It
provides a way to execute a single CNOS command on a network device by
evaluating the current running configuration and executing the command only
if the specific settings have not been already configured.
The CNOS command is passed as an argument of the method.
This module functions the same as the cnos_command module.
The only exception is that following inventory variable can be specified
["condition = <flag string>"]
When this inventory variable is specified as the variable of a task, the
command is executed for the network element that matches the flag string.
Usually, commands are executed across a group of network devices. When
there is a requirement to skip the execution of the command on one or
more devices, it is recommended to use this module. This module uses SSH to
manage network device configuration.
extends_documentation_fragment:
- community.general.cnos
options:
clicommand:
description:
- This specifies the CLI command as an attribute to this method.
The command is passed using double quotes. The variables can be
placed directly on to the CLI commands or can be invoked
from the vars directory.
required: true
default: Null
condition:
description:
- If you specify condition=false in the inventory file against any
device, the command execution is skipped for that device.
required: true
default: Null
flag:
description:
- If a task needs to be executed, you have to set the flag the same
as it is specified in the inventory for that device.
required: true
default: Null
'''
EXAMPLES = '''
Tasks : The following are examples of using the module
cnos_conditional_command. These are written in the main.yml file of the tasks
directory.
---
- name: Applying CLI template on VLAG Tier1 Leaf Switch1
cnos_conditional_command:
deviceType: "{{ hostvars[inventory_hostname]['deviceType'] }}"
outputfile: "./results/test_conditional_command_
{{ inventory_hostname }}_output.txt"
condition: "{{ hostvars[inventory_hostname]['condition']}}"
flag: leaf_switch2
command: "spanning-tree mode enable"
'''
RETURN = '''
msg:
description: Success or failure message
returned: always
type: str
sample: "Command Applied"
'''
import sys
import time
import socket
import array
import json
import time
import re
import os
try:
from ansible_collections.community.general.plugins.module_utils.network.cnos import cnos
HAS_LIB = True
except Exception:
HAS_LIB = False
from ansible.module_utils.basic import AnsibleModule
from collections import defaultdict
def main():
module = AnsibleModule(
argument_spec=dict(
clicommand=dict(required=True),
outputfile=dict(required=True),
condition=dict(required=True),
flag=dict(required=True),
host=dict(required=False),
deviceType=dict(required=True),
username=dict(required=False),
password=dict(required=False, no_log=True),
enablePassword=dict(required=False,
no_log=True), ), supports_check_mode=False)
condition = module.params['condition']
flag = module.params['flag']
cliCommand = module.params['clicommand']
outputfile = module.params['outputfile']
output = ''
if (condition is None or condition != flag):
module.exit_json(changed=True, msg="Command Skipped for this switch")
return ''
# Send the CLi command
cmd = [{'command': cliCommand, 'prompt': None, 'answer': None}]
output = output + str(cnos.run_cnos_commands(module, cmd))
# Write to memory
save_cmd = [{'command': 'save', 'prompt': None, 'answer': None}]
cmd.extend(save_cmd)
output = output + str(cnos.run_cnos_commands(module, cmd))
# Save it into the file
path = outputfile.rsplit('/', 1)
# cnos.debugOutput(path[0])
if not os.path.exists(path[0]):
os.makedirs(path[0])
file = open(outputfile, "a")
file.write(output)
file.close()
# Logic to check when changes occur or not
errorMsg = cnos.checkOutputForError(output)
if(errorMsg is None):
module.exit_json(changed=True,
msg="CLI Command executed and results saved in file ")
else:
module.fail_json(msg=errorMsg)
if __name__ == '__main__':
main()

View file

@ -0,0 +1,188 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
#
# Copyright (C) 2017 Lenovo, Inc.
#
# This file is part of Ansible
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
#
# Module to send conditional template to Lenovo Switches
# Lenovo Networking
#
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = '''
---
module: cnos_conditional_template
author: "Anil Kumar Muraleedharan (@amuraleedhar)"
short_description: Manage switch configuration using templates based on
condition on devices running Lenovo CNOS
description:
- This module allows you to work with the running configuration of a
switch. It provides a way to execute a set of CNOS commands on a switch by
evaluating the current running configuration and executing the commands
only if the specific settings have not been already configured.
The configuration source can be a set of commands or a template written in
the Jinja2 templating language. This module functions the same as the
cnos_template module. The only exception is that the following inventory
variable can be specified.
["condition = <flag string>"]
When this inventory variable is specified as the variable of a task, the
template is executed for the network element that matches the flag string.
Usually, templates are used when commands are the same across a group of
network devices. When there is a requirement to skip the execution of the
template on one or more devices, it is recommended to use this module.
This module uses SSH to manage network device configuration.
extends_documentation_fragment:
- community.general.cnos
options:
commandfile:
description:
- This specifies the path to the CNOS command file which needs to
be applied. This usually comes from the commands folder. Generally
this file is the output of the variables applied on a template
file. So this command is preceded by a template module. The
command file must contain the Ansible keyword
{{ inventory_hostname }} and the condition flag in its filename to
ensure that the command file is unique for each switch and
condition. If this is omitted, the command file will be
overwritten during iteration. For example,
commandfile=./commands/clos_leaf_bgp_
{{ inventory_hostname }}_LP21_commands.txt
required: true
default: Null
condition:
description:
- If you specify condition=<flag string> in the inventory file
against any device, the template execution is done for that device
in case it matches the flag setting for that task.
required: true
default: Null
flag:
description:
- If a task needs to be executed, you have to set the flag the same
as it is specified in the inventory for that device.
required: true
default: Null
'''
EXAMPLES = '''
Tasks : The following are examples of using the module
cnos_conditional_template. These are written in the main.yml file of the
tasks directory.
---
- name: Applying CLI template on VLAG Tier1 Leaf Switch1
cnos_conditional_template:
deviceType: "{{ hostvars[inventory_hostname]['deviceType'] }}"
outputfile: "./results/vlag_1tier_leaf_switch1_
{{ inventory_hostname }}_output.txt"
condition: "{{ hostvars[inventory_hostname]['condition']}}"
flag: "leaf_switch1"
commandfile: "./commands/vlag_1tier_leaf_switch1_
{{ inventory_hostname }}_commands.txt"
stp_mode1: "disable"
port_range1: "17,18,29,30"
portchannel_interface_number1: 1001
portchannel_mode1: active
slot_chassis_number1: 1/48
switchport_mode1: trunk
'''
RETURN = '''
msg:
description: Success or failure message
returned: always
type: str
sample: "Template Applied."
'''
import sys
import time
import socket
import array
import json
import time
import re
import os
try:
from ansible_collections.community.general.plugins.module_utils.network.cnos import cnos
HAS_LIB = True
except Exception:
HAS_LIB = False
from ansible.module_utils.basic import AnsibleModule
from collections import defaultdict
def main():
module = AnsibleModule(
argument_spec=dict(
commandfile=dict(required=True),
outputfile=dict(required=True),
condition=dict(required=True),
flag=dict(required=True),
host=dict(required=False),
deviceType=dict(required=True),
username=dict(required=False),
password=dict(required=False, no_log=True),
enablePassword=dict(required=False, no_log=True),),
supports_check_mode=False)
condition = module.params['condition']
flag = module.params['flag']
commandfile = module.params['commandfile']
outputfile = module.params['outputfile']
output = ''
if (condition is None or condition != flag):
module.exit_json(changed=True, msg="Template Skipped for this switch")
return " "
# Send commands one by one
f = open(commandfile, "r")
cmd = []
for line in f:
# Omit the comment lines in template file
if not line.startswith("#"):
# cnos.debugOutput(line)
command = line.strip()
inner_cmd = [{'command': command, 'prompt': None, 'answer': None}]
cmd.extend(inner_cmd)
# Write to memory
save_cmd = [{'command': 'save', 'prompt': None, 'answer': None}]
cmd.extend(save_cmd)
output = output + str(cnos.run_cnos_commands(module, cmd))
# Write output to file
path = outputfile.rsplit('/', 1)
# cnos.debugOutput(path[0])
if not os.path.exists(path[0]):
os.makedirs(path[0])
file = open(outputfile, "a")
file.write(output)
file.close()
# Logic to check when changes occur or not
errorMsg = cnos.checkOutputForError(output)
if(errorMsg is None):
module.exit_json(changed=True, msg="Template Applied")
else:
module.fail_json(msg=errorMsg)
if __name__ == '__main__':
main()

View file

@ -0,0 +1,307 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
#
# (C) 2017 Red Hat Inc.
# Copyright (C) 2017 Lenovo.
#
# GNU General Public License v3.0+
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
#
# Module to configure Lenovo Switches.
# Lenovo Networking
#
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = '''
---
module: cnos_config
author: "Anil Kumar Muraleedharan (@amuraleedhar)"
short_description: Manage Lenovo CNOS configuration sections
description:
- Lenovo CNOS configurations use a simple block indent file syntax
for segmenting configuration into sections. This module provides
an implementation for working with CNOS configuration sections in
a deterministic way.
notes:
- Tested against CNOS 10.9.1
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.
aliases: ['commands']
parents:
description:
- The ordered set of parents that uniquely identify the section
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.
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).
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.
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.
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.
default: line
choices: ['line', 'strict', 'exact', 'none']
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.
default: line
choices: ['line', 'block', 'config']
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(config) argument allows the
implementer to pass in the configuration to use as the base
config for comparison.
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. If the C(backup_options) value is not given,
the backup file is written to the C(backup) folder in the playbook
root directory. If the directory does not exist, it is created.
type: bool
default: 'no'
comment:
description:
- Allows a commit description to be specified to be included
when the configuration is committed. If the configuration is
not changed or committed, this argument is ignored.
default: 'configured by cnos_config'
admin:
description:
- Enters into administration configuration mode for making config
changes to the device.
type: bool
default: 'no'
backup_options:
description:
- This is a dict object containing configurable options related to backup file path.
The value of this option is read only when C(backup) is set to I(yes), if C(backup) is set
to I(no) this option will be silently ignored.
suboptions:
filename:
description:
- The filename to be used to store the backup configuration. If the filename
is not given it will be generated based on the hostname, current time and date
in format defined by <hostname>_config.<current-date>@<current-time>
dir_path:
description:
- This option provides the path ending with directory name in which the backup
configuration file will be stored. If the directory does not exist it will be first
created and the filename is either the value of C(filename) or default filename
as described in C(filename) options description. If the path value is not given
in that case a I(backup) directory will be created in the current working directory
and backup configuration will be copied in C(filename) within I(backup) directory.
type: path
type: dict
'''
EXAMPLES = """
Tasks: The following are examples of using the module cnos_config.
---
- name: configure top level configuration
cnos_config:
"lines: hostname {{ inventory_hostname }}"
- name: configure interface settings
cnos_config:
lines:
- enable
- ip ospf enable
parents: interface ip 13
- name: load a config from disk and replace the current config
cnos_config:
src: config.cfg
backup: yes
- name: configurable backup path
cnos_config:
src: config.cfg
backup: yes
backup_options:
filename: backup.cfg
dir_path: /home/user
"""
RETURN = """
updates:
description: The set of commands that will be pushed to the remote device
returned: Only when lines is specified.
type: list
sample: ['...', '...']
backup_path:
description: The full path to the backup file
returned: when backup is yes
type: str
sample: /playbooks/ansible/backup/cnos01.2016-07-16@22:28:34
"""
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.community.general.plugins.module_utils.network.cnos.cnos import load_config, get_config
from ansible_collections.community.general.plugins.module_utils.network.cnos.cnos import check_args
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.config import NetworkConfig, dumps
DEFAULT_COMMIT_COMMENT = 'configured by cnos_config'
def get_running_config(module):
contents = module.params['config']
if not contents:
contents = get_config(module)
return NetworkConfig(indent=1, contents=contents)
def get_candidate(module):
candidate = NetworkConfig(indent=1)
if module.params['src']:
candidate.load(module.params['src'])
elif module.params['lines']:
parents = module.params['parents'] or list()
candidate.add(module.params['lines'], parents=parents)
return candidate
def run(module, result):
match = module.params['match']
replace = module.params['replace']
replace_config = replace == 'config'
path = module.params['parents']
comment = module.params['comment']
admin = module.params['admin']
check_mode = module.check_mode
candidate = get_candidate(module)
if match != 'none' and replace != 'config':
contents = get_running_config(module)
configobj = NetworkConfig(contents=contents, indent=1)
commands = candidate.difference(configobj, path=path, match=match,
replace=replace)
else:
commands = candidate.items
if commands:
commands = dumps(commands, 'commands').split('\n')
if any((module.params['lines'], module.params['src'])):
if module.params['before']:
commands[:0] = module.params['before']
if module.params['after']:
commands.extend(module.params['after'])
result['commands'] = commands
diff = load_config(module, commands)
if diff:
result['diff'] = dict(prepared=diff)
result['changed'] = True
def main():
"""main entry point for module execution
"""
backup_spec = dict(
filename=dict(),
dir_path=dict(type='path')
)
argument_spec = dict(
src=dict(type='path'),
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', 'config']),
config=dict(),
backup=dict(type='bool', default=False),
backup_options=dict(type='dict', options=backup_spec),
comment=dict(default=DEFAULT_COMMIT_COMMENT),
admin=dict(type='bool', default=False)
)
mutually_exclusive = [('lines', 'src'),
('parents', 'src')]
required_if = [('match', 'strict', ['lines']),
('match', 'exact', ['lines']),
('replace', 'block', ['lines']),
('replace', 'config', ['src'])]
module = AnsibleModule(argument_spec=argument_spec,
mutually_exclusive=mutually_exclusive,
required_if=required_if,
supports_check_mode=True)
warnings = list()
check_args(module, warnings)
result = dict(changed=False, warnings=warnings)
if module.params['backup']:
result['__backup__'] = get_config(module)
run(module, result)
module.exit_json(**result)
if __name__ == '__main__':
main()

View file

@ -0,0 +1,116 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
#
# Copyright (C) 2017 Lenovo, Inc.
#
# This file is part of Ansible
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
#
# Module to Reset to factory settings of Lenovo Switches
# Lenovo Networking
#
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = '''
---
module: cnos_factory
author: "Anil Kumar Muraleedharan (@amuraleedhar)"
short_description: Reset the switch startup configuration to default (factory)
on devices running Lenovo CNOS.
description:
- This module allows you to reset a switch's startup configuration. The
method provides a way to reset the startup configuration to its factory
settings. This is helpful when you want to move the switch to another
topology as a new network device. This module uses SSH to manage network
device configuration. The result of the operation can be viewed in results
directory.
extends_documentation_fragment:
- community.general.cnos
options: {}
'''
EXAMPLES = '''
Tasks : The following are examples of using the module cnos_reload. These are
written in the main.yml file of the tasks directory.
---
- name: Test Reset to factory
cnos_factory:
deviceType: "{{ hostvars[inventory_hostname]['deviceType'] }}"
outputfile: "./results/test_factory_{{ inventory_hostname }}_output.txt"
'''
RETURN = '''
msg:
description: Success or failure message
returned: always
type: str
sample: "Switch Startup Config is Reset to factory settings"
'''
import sys
import time
import socket
import array
import json
import time
import re
try:
from ansible_collections.community.general.plugins.module_utils.network.cnos import cnos
HAS_LIB = True
except Exception:
HAS_LIB = False
from ansible.module_utils.basic import AnsibleModule
from collections import defaultdict
def main():
module = AnsibleModule(
argument_spec=dict(
outputfile=dict(required=True),
host=dict(required=False),
username=dict(required=False),
password=dict(required=False, no_log=True),
enablePassword=dict(required=False, no_log=True),
deviceType=dict(required=True),),
supports_check_mode=False)
command = 'write erase'
outputfile = module.params['outputfile']
output = ''
cmd = [{'command': command, 'prompt': '[n]', 'answer': 'y'}]
output = output + str(cnos.run_cnos_commands(module, cmd))
# Save it into the file
file = open(outputfile, "a")
file.write(output)
file.close()
errorMsg = cnos.checkOutputForError(output)
if(errorMsg is None):
module.exit_json(changed=True,
msg="Switch Startup Config is Reset to Factory settings")
else:
module.fail_json(msg=errorMsg)
if __name__ == '__main__':
main()

View file

@ -0,0 +1,539 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
#
# (C) 2019 Red Hat Inc.
# Copyright (C) 2019 Lenovo.
#
# GNU General Public License v3.0+
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
# Module to Collect facts from Lenovo Switches running Lenovo CNOS commands
# Lenovo Networking
#
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = '''
---
module: cnos_facts
author: "Anil Kumar Muraleedharan (@amuraleedhar)"
short_description: Collect facts from remote devices running Lenovo CNOS
description:
- Collects a base set of device facts from a remote Lenovo device
running on CNOS. 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 CNOS 10.8.1
options:
authorize:
description:
- Instructs the module to enter privileged mode on the remote device
before sending any commands. If not specified, the device will
attempt to execute all commands in non-privileged mode. If the value
is not specified in the task, the value of environment variable
C(ANSIBLE_NET_AUTHORIZE) will be used instead.
type: bool
default: 'no'
auth_pass:
description:
- Specifies the password to use if required to enter privileged mode
on the remote device. If I(authorize) is false, then this argument
does nothing. If the value is not specified in the task, the value of
environment variable C(ANSIBLE_NET_AUTH_PASS) will be used instead.
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
default: '!config'
'''
EXAMPLES = '''
Tasks: The following are examples of using the module cnos_facts.
---
- name: Test cnos Facts
cnos_facts:
---
# Collect all facts from the device
- cnos_facts:
gather_subset: all
# Collect only the config and default facts
- cnos_facts:
gather_subset:
- config
# Do not collect hardware facts
- cnos_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 Lenovo CNOS device
returned: always
type: str
ansible_net_serialnum:
description: The serial number of the Lenovo CNOS device
returned: always
type: str
ansible_net_version:
description: The CNOS 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: Indicates the active image for the device
returned: always
type: str
# hardware
ansible_net_memfree_mb:
description: The available free 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.
This gives information on description, mac address, mtu, speed,
duplex and operstatus
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.cnos.cnos import run_commands
from ansible_collections.community.general.plugins.module_utils.network.cnos.cnos import check_args
from ansible.module_utils._text import to_text
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
self.PERSISTENT_COMMAND_TIMEOUT = 60
def populate(self):
self.responses = run_commands(self.module, self.COMMANDS,
check_rc=False)
def run(self, cmd):
return run_commands(self.module, cmd, check_rc=False)
class Default(FactsBase):
COMMANDS = ['show sys-info', 'show running-config']
def populate(self):
super(Default, self).populate()
data = self.responses[0]
data_run = self.responses[1]
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)
if data_run:
self.facts['hostname'] = self.parse_hostname(data_run)
def parse_version(self, data):
for line in data.split('\n'):
line = line.strip()
match = re.match(r'System Software Revision (.*?)',
line, re.M | re.I)
if match:
vers = line.split(':')
ver = vers[1].strip()
return ver
return "NA"
def parse_hostname(self, data_run):
for line in data_run.split('\n'):
line = line.strip()
match = re.match(r'hostname (.*?)', line, re.M | re.I)
if match:
hosts = line.split()
hostname = hosts[1].strip('\"')
return hostname
return "NA"
def parse_model(self, data):
for line in data.split('\n'):
line = line.strip()
match = re.match(r'System Model (.*?)', line, re.M | re.I)
if match:
mdls = line.split(':')
mdl = mdls[1].strip()
return mdl
return "NA"
def parse_image(self, data):
match = re.search(r'(.*) image(.*)', data, re.M | re.I)
if match:
return "Image1"
else:
return "Image2"
def parse_serialnum(self, data):
for line in data.split('\n'):
line = line.strip()
match = re.match(r'System Serial Number (.*?)', line, re.M | re.I)
if match:
serNums = line.split(':')
ser = serNums[1].strip()
return ser
return "NA"
class Hardware(FactsBase):
COMMANDS = [
'show running-config'
]
def populate(self):
super(Hardware, self).populate()
data = self.run(['show process memory'])
data = to_text(data, errors='surrogate_or_strict').strip()
data = data.replace(r"\n", "\n")
if data:
for line in data.split('\n'):
line = line.strip()
match = re.match(r'Mem: (.*?)', line, re.M | re.I)
if match:
memline = line.split(':')
mems = memline[1].strip().split()
self.facts['memtotal_mb'] = int(mems[0]) / 1024
self.facts['memused_mb'] = int(mems[1]) / 1024
self.facts['memfree_mb'] = int(mems[2]) / 1024
self.facts['memshared_mb'] = int(mems[3]) / 1024
self.facts['memavailable_mb'] = int(mems[5]) / 1024
def parse_memtotal(self, data):
match = re.search(r'^MemTotal:\s*(.*) kB', data, re.M | re.I)
if match:
return int(match.group(1)) / 1024
def parse_memfree(self, data):
match = re.search(r'^MemFree:\s*(.*) kB', data, re.M | re.I)
if match:
return int(match.group(1)) / 1024
class Config(FactsBase):
COMMANDS = ['show running-config']
def populate(self):
super(Config, self).populate()
data = self.responses[0]
if data:
self.facts['config'] = data
class Interfaces(FactsBase):
COMMANDS = ['show interface brief']
def populate(self):
super(Interfaces, self).populate()
self.facts['all_ipv4_addresses'] = list()
self.facts['all_ipv6_addresses'] = list()
data1 = self.run(['show interface status'])
data1 = to_text(data1, errors='surrogate_or_strict').strip()
data1 = data1.replace(r"\n", "\n")
data2 = self.run(['show interface mac-address'])
data2 = to_text(data2, errors='surrogate_or_strict').strip()
data2 = data2.replace(r"\n", "\n")
lines1 = None
lines2 = None
if data1:
lines1 = self.parse_interfaces(data1)
if data2:
lines2 = self.parse_interfaces(data2)
if lines1 is not None and lines2 is not None:
self.facts['interfaces'] = self.populate_interfaces(lines1, lines2)
data3 = self.run(['show lldp neighbors'])
data3 = to_text(data3, errors='surrogate_or_strict').strip()
data3 = data3.replace(r"\n", "\n")
if data3:
lines3 = self.parse_neighbors(data3)
if lines3 is not None:
self.facts['neighbors'] = self.populate_neighbors(lines3)
data4 = self.run(['show ip interface brief vrf all'])
data5 = self.run(['show ipv6 interface brief vrf all'])
data4 = to_text(data4, errors='surrogate_or_strict').strip()
data4 = data4.replace(r"\n", "\n")
data5 = to_text(data5, errors='surrogate_or_strict').strip()
data5 = data5.replace(r"\n", "\n")
lines4 = None
lines5 = None
if data4:
lines4 = self.parse_ipaddresses(data4)
ipv4_interfaces = self.set_ip_interfaces(lines4)
self.facts['all_ipv4_addresses'] = ipv4_interfaces
if data5:
lines5 = self.parse_ipaddresses(data5)
ipv6_interfaces = self.set_ipv6_interfaces(lines5)
self.facts['all_ipv6_addresses'] = ipv6_interfaces
def parse_ipaddresses(self, data):
parsed = list()
for line in data.split('\n'):
if len(line) == 0:
continue
else:
line = line.strip()
match = re.match(r'^(Ethernet+)', line)
if match:
key = match.group(1)
parsed.append(line)
match = re.match(r'^(po+)', line)
if match:
key = match.group(1)
parsed.append(line)
match = re.match(r'^(mgmt+)', line)
if match:
key = match.group(1)
parsed.append(line)
match = re.match(r'^(loopback+)', line)
if match:
key = match.group(1)
parsed.append(line)
return parsed
def populate_interfaces(self, lines1, lines2):
interfaces = dict()
for line1, line2 in zip(lines1, lines2):
line = line1 + " " + line2
intfSplit = line.split()
innerData = dict()
innerData['description'] = intfSplit[1].strip()
innerData['macaddress'] = intfSplit[8].strip()
innerData['type'] = intfSplit[6].strip()
innerData['speed'] = intfSplit[5].strip()
innerData['duplex'] = intfSplit[4].strip()
innerData['operstatus'] = intfSplit[2].strip()
interfaces[intfSplit[0].strip()] = innerData
return interfaces
def parse_interfaces(self, data):
parsed = list()
for line in data.split('\n'):
if len(line) == 0:
continue
else:
line = line.strip()
match = re.match(r'^(Ethernet+)', line)
if match:
key = match.group(1)
parsed.append(line)
match = re.match(r'^(po+)', line)
if match:
key = match.group(1)
parsed.append(line)
match = re.match(r'^(mgmt+)', line)
if match:
key = match.group(1)
parsed.append(line)
return parsed
def set_ip_interfaces(self, line4):
ipv4_addresses = list()
for line in line4:
ipv4Split = line.split()
if 'Ethernet' in ipv4Split[0]:
ipv4_addresses.append(ipv4Split[1])
if 'mgmt' in ipv4Split[0]:
ipv4_addresses.append(ipv4Split[1])
if 'po' in ipv4Split[0]:
ipv4_addresses.append(ipv4Split[1])
if 'loopback' in ipv4Split[0]:
ipv4_addresses.append(ipv4Split[1])
return ipv4_addresses
def set_ipv6_interfaces(self, line4):
ipv6_addresses = list()
for line in line4:
ipv6Split = line.split()
if 'Ethernet' in ipv6Split[0]:
ipv6_addresses.append(ipv6Split[1])
if 'mgmt' in ipv6Split[0]:
ipv6_addresses.append(ipv6Split[1])
if 'po' in ipv6Split[0]:
ipv6_addresses.append(ipv6Split[1])
if 'loopback' in ipv6Split[0]:
ipv6_addresses.append(ipv6Split[1])
return ipv6_addresses
def populate_neighbors(self, lines3):
neighbors = dict()
device_name = ''
for line in lines3:
neighborSplit = line.split()
innerData = dict()
count = len(neighborSplit)
if count == 5:
local_interface = neighborSplit[1].strip()
innerData['Device Name'] = neighborSplit[0].strip()
innerData['Hold Time'] = neighborSplit[2].strip()
innerData['Capability'] = neighborSplit[3].strip()
innerData['Remote Port'] = neighborSplit[4].strip()
neighbors[local_interface] = innerData
elif count == 4:
local_interface = neighborSplit[0].strip()
innerData['Hold Time'] = neighborSplit[1].strip()
innerData['Capability'] = neighborSplit[2].strip()
innerData['Remote Port'] = neighborSplit[3].strip()
neighbors[local_interface] = innerData
return neighbors
def parse_neighbors(self, neighbors):
parsed = list()
for line in neighbors.split('\n'):
if len(line) == 0:
continue
else:
line = line.strip()
if 'Ethernet' in line:
parsed.append(line)
if 'mgmt' in line:
parsed.append(line)
if 'po' in line:
parsed.append(line)
if 'loopback' in line:
parsed.append(line)
return parsed
FACT_SUBSETS = dict(
default=Default,
hardware=Hardware,
interfaces=Interfaces,
config=Config,
)
VALID_SUBSETS = frozenset(FACT_SUBSETS.keys())
PERSISTENT_COMMAND_TIMEOUT = 60
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
warnings = list()
check_args(module, warnings)
module.exit_json(ansible_facts=ansible_facts, warnings=warnings)
if __name__ == '__main__':
main()

View file

@ -0,0 +1,241 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
#
# Copyright (C) 2017 Lenovo, Inc.
#
# This file is part of Ansible
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
#
# Module to download new image to Lenovo Switches
# Lenovo Networking
#
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = '''
---
module: cnos_image
author: "Anil Kumar Muraleedharan (@amuraleedhar)"
short_description: Perform firmware upgrade/download from a remote server on
devices running Lenovo CNOS
description:
- This module allows you to work with switch firmware images. It provides a
way to download a firmware image to a network device from a remote server
using FTP, SFTP, TFTP, or SCP. The first step is to create a directory
from where the remote server can be reached. The next step is to provide
the full file path of the image's location. Authentication details
required by the remote server must be provided as well. By default, this
method makes the newly downloaded firmware image the active image, which
will be used by the switch during the next restart.
This module uses SSH to manage network device configuration.
The results of the operation will be placed in a directory named 'results'
that must be created by the user in their local directory to where the
playbook is run.
extends_documentation_fragment:
- community.general.cnos
options:
protocol:
description:
- This refers to the protocol used by the network device to
interact with the remote server from where to download the
firmware image. The choices are FTP, SFTP, TFTP, or SCP. Any other
protocols will result in error. If this parameter is not specified
there is no default value to be used.
required: true
choices: [SFTP, SCP, FTP, TFTP]
serverip:
description:
- This specifies the IP Address of the remote server from where the
software image will be downloaded.
required: true
imgpath:
description:
- This specifies the full file path of the image located on the
remote server. In case the relative path is used as the variable
value, the root folder for the user of the server needs to be
specified.
required: true
imgtype:
description:
- This specifies the firmware image type to be downloaded
required: true
choices: [all, boot, os, onie]
serverusername:
description:
- Specify the username for the server relating to the protocol used
required: true
serverpassword:
description:
- Specify the password for the server relating to the protocol used
'''
EXAMPLES = '''
Tasks : The following are examples of using the module cnos_image. These are
written in the main.yml file of the tasks directory.
---
- name: Test Image transfer
cnos_image:
deviceType: "{{ hostvars[inventory_hostname]['deviceType'] }}"
outputfile: "./results/test_image_{{ inventory_hostname }}_output.txt"
protocol: "sftp"
serverip: "10.241.106.118"
imgpath: "/root/cnos_images/G8272-10.1.0.112.img"
imgtype: "os"
serverusername: "root"
serverpassword: "root123"
- name: Test Image tftp
cnos_image:
deviceType: "{{ hostvars[inventory_hostname]['deviceType'] }}"
outputfile: "./results/test_image_{{ inventory_hostname }}_output.txt"
protocol: "tftp"
serverip: "10.241.106.118"
imgpath: "/anil/G8272-10.2.0.34.img"
imgtype: "os"
serverusername: "root"
serverpassword: "root123"
'''
RETURN = '''
msg:
description: Success or failure message
returned: always
type: str
sample: "Image file transferred to device"
'''
import sys
import time
import socket
import array
import json
import time
import re
import os
try:
from ansible_collections.community.general.plugins.module_utils.network.cnos import cnos
HAS_LIB = True
except Exception:
HAS_LIB = False
from ansible.module_utils.basic import AnsibleModule
from collections import defaultdict
def doImageDownload(module, prompt, answer):
protocol = module.params['protocol'].lower()
server = module.params['serverip']
imgPath = module.params['imgpath']
imgType = module.params['imgtype']
username = module.params['serverusername']
password = module.params['serverpassword']
retVal = ''
command = "copy " + protocol + " " + protocol + "://" + username + "@"
command = command + server + "/" + imgPath + " system-image "
command = command + imgType + " vrf management"
cmd = []
if(protocol == "scp"):
prompt = ['timeout', 'Confirm download operation', 'Password',
'Do you want to change that to the standby image']
answer = ['240', 'y', password, 'y']
scp_cmd = [{'command': command, 'prompt': prompt, 'answer': answer,
'check_all': True}]
cmd.extend(scp_cmd)
retVal = retVal + str(cnos.run_cnos_commands(module, cmd))
elif(protocol == "sftp"):
prompt = ['Confirm download operation', 'Password',
'Do you want to change that to the standby image']
answer = ['y', password, 'y']
sftp_cmd = [{'command': command, 'prompt': prompt, 'answer': answer,
'check_all': True}]
cmd.extend(sftp_cmd)
retVal = retVal + str(cnos.run_cnos_commands(module, cmd))
elif(protocol == "ftp"):
prompt = ['Confirm download operation', 'Password',
'Do you want to change that to the standby image']
answer = ['y', password, 'y']
ftp_cmd = [{'command': command, 'prompt': prompt, 'answer': answer,
'check_all': True}]
cmd.extend(ftp_cmd)
retVal = retVal + str(cnos.run_cnos_commands(module, cmd))
elif(protocol == "tftp"):
command = "copy " + protocol + " " + protocol + "://" + server
command = command + "/" + imgPath + " system-image " + imgType
command = command + " vrf management"
prompt = ['Confirm download operation',
'Do you want to change that to the standby image']
answer = ['y', 'y']
tftp_cmd = [{'command': command, 'prompt': prompt, 'answer': answer,
'check_all': True}]
cmd.extend(tftp_cmd)
retVal = retVal + str(cnos.run_cnos_commands(module, cmd))
else:
return "Error-110"
return retVal
# EOM
def main():
module = AnsibleModule(
argument_spec=dict(
outputfile=dict(required=True),
host=dict(required=False),
username=dict(required=False),
password=dict(required=False, no_log=True),
enablePassword=dict(required=False, no_log=True),
deviceType=dict(required=True),
protocol=dict(required=True),
serverip=dict(required=True),
imgpath=dict(required=True),
imgtype=dict(required=True),
serverusername=dict(required=False),
serverpassword=dict(required=False, no_log=True),),
supports_check_mode=False)
outputfile = module.params['outputfile']
protocol = module.params['protocol'].lower()
output = ''
# Invoke method for image transfer from server
if(protocol == "tftp" or protocol == "ftp" or protocol == "sftp" or
protocol == "scp"):
transfer_status = doImageDownload(module, None, None)
else:
transfer_status = "Invalid Protocol option"
output = output + "\n Image Transfer status \n" + transfer_status
# Save it into the file
path = outputfile.rsplit('/', 1)
if not os.path.exists(path[0]):
os.makedirs(path[0])
file = open(outputfile, "a")
file.write(output)
file.close()
# Logic to check when changes occur or not
errorMsg = cnos.checkOutputForError(output)
if(errorMsg is None):
module.exit_json(changed=True, msg="Image file transferred to device")
else:
module.fail_json(msg=errorMsg)
if __name__ == '__main__':
main()

View file

@ -0,0 +1,555 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
#
# Copyright (C) 2017 Lenovo, Inc.
# (c) 2017, Ansible by Red Hat, inc
# This file is part of Ansible
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
#
# Module to work on Interfaces with Lenovo Switches
# Lenovo Networking
#
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = '''
---
module: cnos_interface
author: "Anil Kumar Muraleedharan(@amuraleedhar)"
short_description: Manage Interface on Lenovo CNOS network devices
description:
- This module provides declarative management of Interfaces
on Lenovo CNOS network devices.
notes:
- Tested against CNOS 10.8.1
options:
name:
description:
- Name of the Interface.
required: true
description:
description:
- Description of Interface.
enabled:
description:
- Interface link status.
type: bool
default: True
speed:
description:
- Interface link speed.
mtu:
description:
- Maximum size of transmit packet.
duplex:
description:
- Interface link status
default: auto
choices: ['full', 'half', 'auto']
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)
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)
neighbors:
description:
- Check operational state of given interface C(name) for LLDP neighbor.
- The following suboptions are available.
suboptions:
host:
description:
- "LLDP neighbor host for given interface C(name)."
port:
description:
- "LLDP neighbor port to which interface C(name) is connected."
aggregate:
description: List of Interfaces definitions.
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)
default: 20
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
choices: ['present', 'absent', 'up', 'down']
provider:
description:
- B(Deprecated)
- "Starting with Ansible 2.5 we recommend using C(connection: network_cli)."
- For more information please see the L(CNOS Platform Options guide, ../network/user_guide/platform_cnos.html).
- HORIZONTALLINE
- A dict object containing connection details.
suboptions:
host:
description:
- Specifies the DNS host name or address for connecting to the remote
device over the specified transport. The value of host is used as
the destination address for the transport.
required: true
port:
description:
- Specifies the port to use when building the connection to the remote device.
default: 22
username:
description:
- Configures the username to use to authenticate the connection to
the remote device. This value is used to authenticate
the SSH session. If the value is not specified in the task, the
value of environment variable C(ANSIBLE_NET_USERNAME) will be used instead.
password:
description:
- Specifies the password to use to authenticate the connection to
the remote device. This value is used to authenticate
the SSH session. If the value is not specified in the task, the
value of environment variable C(ANSIBLE_NET_PASSWORD) will be used instead.
timeout:
description:
- Specifies the timeout in seconds for communicating with the network device
for either connecting or sending commands. If the timeout is
exceeded before the operation is completed, the module will error.
default: 10
ssh_keyfile:
description:
- Specifies the SSH key to use to authenticate the connection to
the remote device. This value is the path to the
key used to authenticate the SSH session. If the value is not specified
in the task, the value of environment variable C(ANSIBLE_NET_SSH_KEYFILE)
will be used instead.
authorize:
description:
- Instructs the module to enter privileged mode on the remote device
before sending any commands. If not specified, the device will
attempt to execute all commands in non-privileged mode. If the value
is not specified in the task, the value of environment variable
C(ANSIBLE_NET_AUTHORIZE) will be used instead.
type: bool
default: 'no'
auth_pass:
description:
- Specifies the password to use if required to enter privileged mode
on the remote device. If I(authorize) is false, then this argument
does nothing. If the value is not specified in the task, the value of
environment variable C(ANSIBLE_NET_AUTH_PASS) will be used instead.
'''
EXAMPLES = """
- name: configure interface
cnos_interface:
name: Ethernet1/33
description: test-interface
speed: 100
duplex: half
mtu: 999
- name: remove interface
cnos_interface:
name: loopback3
state: absent
- name: make interface up
cnos_interface:
name: Ethernet1/33
enabled: True
- name: make interface down
cnos_interface:
name: Ethernet1/33
enabled: False
- name: Check intent arguments
cnos_interface:
name: Ethernet1/33
state: up
tx_rate: ge(0)
rx_rate: le(0)
- name: Check neighbors intent arguments
cnos_interface:
name: Ethernet1/33
neighbors:
- port: eth0
host: netdev
- name: Config + intent
cnos_interface:
name: Ethernet1/33
enabled: False
state: down
- name: Add interface using aggregate
cnos_interface:
aggregate:
- { name: Ethernet1/33, mtu: 256, description: test-interface-1 }
- { name: Ethernet1/44, mtu: 516, description: test-interface-2 }
duplex: full
speed: 100
state: present
- name: Delete interface using aggregate
cnos_interface:
aggregate:
- name: loopback3
- name: loopback6
state: absent
"""
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 Ethernet1/33
- description test-interface
- duplex half
- mtu 512
"""
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
from ansible.module_utils.connection import exec_command
from ansible_collections.community.general.plugins.module_utils.network.cnos.cnos import get_config, load_config
from ansible_collections.community.general.plugins.module_utils.network.cnos.cnos import cnos_argument_spec
from ansible_collections.community.general.plugins.module_utils.network.cnos.cnos import debugOutput, check_args
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 conditional
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import remove_default_spec
def validate_mtu(value, module):
if value and not 64 <= int(value) <= 9216:
module.fail_json(msg='mtu must be between 64 and 9216')
def validate_param_values(module, obj, param=None):
if param is None:
param = module.params
for key in obj:
# validate the param value (if validator func exists)
validator = globals().get('validate_%s' % key)
if callable(validator):
validator(param.get(key), module)
def parse_shutdown(configobj, name):
cfg = configobj['interface %s' % name]
cfg = '\n'.join(cfg.children)
match = re.search(r'^shutdown', cfg, re.M)
if match:
return True
else:
return False
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 search_obj_in_list(name, lst):
for o in lst:
if o['name'] == name:
return o
return None
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):
config = get_config(module)
configobj = NetworkConfig(indent=1, contents=config)
match = re.findall(r'^interface (\S+)', config, re.M)
if not match:
return list()
instances = list()
for item in set(match):
obj = {
'name': item,
'description': parse_config_argument(configobj, item, 'description'),
'speed': parse_config_argument(configobj, item, 'speed'),
'duplex': parse_config_argument(configobj, item, 'duplex'),
'mtu': parse_config_argument(configobj, item, 'mtu'),
'disable': True if parse_shutdown(configobj, item) else False,
'state': 'present'
}
instances.append(obj)
return instances
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)
d = item.copy()
if d['enabled']:
d['disable'] = False
else:
d['disable'] = True
obj.append(d)
else:
params = {
'name': module.params['name'],
'description': module.params['description'],
'speed': module.params['speed'],
'mtu': module.params['mtu'],
'duplex': module.params['duplex'],
'state': module.params['state'],
'delay': module.params['delay'],
'tx_rate': module.params['tx_rate'],
'rx_rate': module.params['rx_rate'],
'neighbors': module.params['neighbors']
}
validate_param_values(module, params)
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', 'description', 'duplex', 'mtu')
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 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 != running:
if candidate:
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, 'shutdown', commands)
elif not disable and obj_in_have.get('disable', False):
add_command_to_interface(interface, 'no shutdown', commands)
else:
commands.append(interface)
for item in args:
value = w.get(item)
if value:
commands.append(item + ' ' + str(value))
if disable:
commands.append('no shutdown')
return commands
def check_declarative_intent_params(module, want, result):
failed_conditions = []
have_neighbors_lldp = 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 interface %s brief' % 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'):
state_data = out.strip().lower().split(w['name'])
have_state = None
have_state = state_data[1].split()[3]
if have_state is None or not conditional(want_state, have_state.strip()):
failed_conditions.append('state ' + 'eq(%s)' % want_state)
command = 'show interface %s' % w['name']
rc, out, err = exec_command(module, command)
have_tx_rate = None
have_rx_rate = None
rates = out.splitlines()
for s in rates:
s = s.strip()
if 'output rate' in s and 'input rate' in s:
sub = s.split()
if want_tx_rate:
have_tx_rate = sub[8]
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:
have_rx_rate = sub[2]
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 = []
# Process LLDP neighbors
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 ID: ')
for line in lines:
field = line.split('\n')
if field[0].strip() == w['name']:
for item in field:
if item.startswith('System Name:'):
have_host.append(item.split(':')[1].strip())
if item.startswith('Port Description:'):
have_port.append(item.split(':')[1].strip())
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
"""
neighbors_spec = dict(
host=dict(),
port=dict()
)
element_spec = dict(
name=dict(),
description=dict(),
speed=dict(),
mtu=dict(),
duplex=dict(default='auto', choices=['full', 'half', 'auto']),
enabled=dict(default=True, type='bool'),
tx_rate=dict(),
rx_rate=dict(),
neighbors=dict(type='list', elements='dict', options=neighbors_spec),
delay=dict(default=20, type='int'),
state=dict(default='present',
choices=['present', 'absent', 'up', 'down'])
)
aggregate_spec = deepcopy(element_spec)
aggregate_spec['name'] = dict(required=True)
# remove default in aggregate spec, to handle common arguments
remove_default_spec(aggregate_spec)
argument_spec = dict(
aggregate=dict(type='list', elements='dict', options=aggregate_spec),
)
argument_spec.update(element_spec)
argument_spec.update(cnos_argument_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()
check_args(module, warnings)
result = {'changed': False}
if warnings:
result['warnings'] = warnings
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,598 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
#
# Copyright (C) 2017 Lenovo, Inc.
# (c) 2017, Ansible by Red Hat, inc
# This file is part of Ansible
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
#
# Module to send banner commands to Lenovo Switches
# Two types of banners are supported login and motd
# Lenovo Networking
#
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = '''
---
module: cnos_l2_interface
short_description: Manage Layer-2 interface on Lenovo CNOS devices.
description:
- This module provides declarative management of Layer-2 interfaces on
Lenovo CNOS devices.
author:
- Anil Kumar Muraleedharan (@amuraleedhar)
options:
name:
description:
- Full name of the interface excluding any logical
unit number, i.e. Ethernet1/3.
required: true
aliases: ['interface']
mode:
description:
- Mode in which interface needs to be configured.
default: access
choices: ['access', 'trunk']
access_vlan:
description:
- Configure given VLAN in access port.
If C(mode=access), used as the access VLAN ID.
trunk_vlans:
description:
- List of VLANs to be configured in trunk port.
If C(mode=trunk), used as the VLAN range to ADD or REMOVE
from the trunk.
native_vlan:
description:
- Native VLAN to be configured in trunk port.
If C(mode=trunk), used as the trunk native VLAN ID.
trunk_allowed_vlans:
description:
- List of allowed VLANs in a given trunk port.
If C(mode=trunk), these are the only VLANs that will be
configured on the trunk, i.e. "2-10,15".
aggregate:
description:
- List of Layer-2 interface definitions.
state:
description:
- Manage the state of the Layer-2 Interface configuration.
default: present
choices: ['present','absent', 'unconfigured']
provider:
description:
- B(Deprecated)
- "Starting with Ansible 2.5 we recommend using
C(connection: network_cli)."
- For more information please see the
L(CNOS Platform Options guide, ../network/user_guide/platform_cnos.html).
- HORIZONTALLINE
- A dict object containing connection details.
suboptions:
host:
description:
- Specifies the DNS host name or address for connecting to the remote
device over the specified transport. The value of host is used as
the destination address for the transport.
required: true
port:
description:
- Specifies the port to use when building the connection to the
remote device.
default: 22
username:
description:
- Configures the username to use to authenticate the connection to
the remote device. This value is used to authenticate
the SSH session. If the value is not specified in the task, the
value of environment variable C(ANSIBLE_NET_USERNAME) will be used
instead.
password:
description:
- Specifies the password to use to authenticate the connection to
the remote device. This value is used to authenticate
the SSH session. If the value is not specified in the task, the
value of environment variable C(ANSIBLE_NET_PASSWORD) will be used
instead.
timeout:
description:
- Specifies the timeout in seconds for communicating with the network
device for either connecting or sending commands. If the timeout
is exceeded before the operation is completed, the module will
error.
default: 10
ssh_keyfile:
description:
- Specifies the SSH key to use to authenticate the connection to
the remote device. This value is the path to the
key used to authenticate the SSH session. If the value is not
specified in the task, the value of environment variable
C(ANSIBLE_NET_SSH_KEYFILE)will be used instead.
authorize:
description:
- Instructs the module to enter privileged mode on the remote device
before sending any commands. If not specified, the device will
attempt to execute all commands in non-privileged mode. If the
value is not specified in the task, the value of environment
variable C(ANSIBLE_NET_AUTHORIZE) will be used instead.
type: bool
default: 'no'
auth_pass:
description:
- Specifies the password to use if required to enter privileged mode
on the remote device. If I(authorize) is false, then this argument
does nothing. If the value is not specified in the task, the value
of environment variable C(ANSIBLE_NET_AUTH_PASS) will be used
instead.
'''
EXAMPLES = """
- name: Ensure Ethernet1/5 is in its default l2 interface state
cnos_l2_interface:
name: Ethernet1/5
state: unconfigured
- name: Ensure Ethernet1/5 is configured for access vlan 20
cnos_l2_interface:
name: Ethernet1/5
mode: access
access_vlan: 20
- name: Ensure Ethernet1/5 only has vlans 5-10 as trunk vlans
cnos_l2_interface:
name: Ethernet1/5
mode: trunk
native_vlan: 10
trunk_vlans: 5-10
- name: Ensure Ethernet1/5 is a trunk port and ensure 2-50 are being tagged
(doesn't mean others aren't also being tagged)
cnos_l2_interface:
name: Ethernet1/5
mode: trunk
native_vlan: 10
trunk_vlans: 2-50
- name: Ensure these VLANs are not being tagged on the trunk
cnos_l2_interface:
name: Ethernet1/5
mode: trunk
trunk_vlans: 51-4094
state: absent
"""
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 Ethernet1/5
- switchport access vlan 20
"""
import re
from copy import deepcopy
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import remove_default_spec
from ansible_collections.community.general.plugins.module_utils.network.cnos.cnos import get_config, load_config
from ansible_collections.community.general.plugins.module_utils.network.cnos.cnos import cnos_argument_spec
from ansible_collections.community.general.plugins.module_utils.network.cnos.cnos import run_commands
def get_interface_type(interface):
intf_type = 'unknown'
if interface.upper()[:2] in ('ET', 'GI', 'FA', 'TE', 'FO', 'HU', 'TWE'):
intf_type = 'ethernet'
elif interface.upper().startswith('VL'):
intf_type = 'svi'
elif interface.upper().startswith('LO'):
intf_type = 'loopback'
elif interface.upper()[:2] in ('MG', 'MA'):
intf_type = 'management'
elif interface.upper().startswith('PO'):
intf_type = 'portchannel'
elif interface.upper().startswith('NV'):
intf_type = 'nve'
return intf_type
def is_switchport(name, module):
intf_type = get_interface_type(name)
if intf_type in ('ethernet', 'portchannel'):
config = run_commands(module,
['show interface {0} switchport'.format(name)])[0]
match = re.search(r'Switchport : enabled', config)
return bool(match)
return False
def interface_is_portchannel(name, module):
if get_interface_type(name) == 'ethernet':
config = run_commands(module, ['show run interface {0}'.format(name)])[0]
if any(c in config for c in ['channel group', 'channel-group']):
return True
return False
def get_switchport(name, module):
config = run_commands(module,
['show interface {0} switchport'.format(name)])[0]
mode = re.search(r'Switchport mode : (?:.* )?(\w+)$', config, re.M)
access = re.search(r'Configured Vlans : (\d+)', config)
native = re.search(r'Default/Native Vlan : (\d+)', config)
trunk = re.search(r'Enabled Vlans : (.+)$', config, re.M)
if mode:
mode = mode.group(1)
if access:
access = access.group(1)
if native:
native = native.group(1)
if trunk:
trunk = trunk.group(1)
if trunk == 'ALL':
trunk = '1-4094'
switchport_config = {
"interface": name,
"mode": mode,
"access_vlan": access,
"native_vlan": native,
"trunk_vlans": trunk,
}
return switchport_config
def remove_switchport_config_commands(name, existing, proposed, module):
mode = proposed.get('mode')
commands = []
command = None
if mode == 'access':
av_check = existing.get('access_vlan') == proposed.get('access_vlan')
if av_check:
command = 'no switchport access vlan'
commands.append(command)
elif mode == 'trunk':
# Supported Remove Scenarios for trunk_vlans_list
# 1) Existing: 1,2,3 Proposed: 1,2,3 - Remove all
# 2) Existing: 1,2,3 Proposed: 1,2 - Remove 1,2 Leave 3
# 3) Existing: 1,2,3 Proposed: 2,3 - Remove 2,3 Leave 1
# 4) Existing: 1,2,3 Proposed: 4,5,6 - None removed.
# 5) Existing: None Proposed: 1,2,3 - None removed.
existing_vlans = existing.get('trunk_vlans_list')
proposed_vlans = proposed.get('trunk_vlans_list')
vlans_to_remove = set(proposed_vlans).intersection(existing_vlans)
if vlans_to_remove:
proposed_allowed_vlans = proposed.get('trunk_allowed_vlans')
remove_trunk_allowed_vlans = proposed.get('trunk_vlans',
proposed_allowed_vlans)
command = 'switchport trunk allowed vlan remove {0}'
command = command.format(remove_trunk_allowed_vlans)
commands.append(command)
native_check = existing.get('native_vlan') == proposed.get('native_vlan')
if native_check and proposed.get('native_vlan'):
command = 'no switchport trunk native vlan'
commands.append(command)
if commands:
commands.insert(0, 'interface ' + name)
return commands
def get_switchport_config_commands(name, existing, proposed, module):
"""Gets commands required to config a given switchport interface
"""
proposed_mode = proposed.get('mode')
existing_mode = existing.get('mode')
commands = []
command = None
if proposed_mode != existing_mode:
if proposed_mode == 'trunk':
command = 'switchport mode trunk'
elif proposed_mode == 'access':
command = 'switchport mode access'
if command:
commands.append(command)
if proposed_mode == 'access':
av_check = str(existing.get('access_vlan')) == str(proposed.get('access_vlan'))
if not av_check:
command = 'switchport access vlan {0}'.format(proposed.get('access_vlan'))
commands.append(command)
elif proposed_mode == 'trunk':
tv_check = existing.get('trunk_vlans_list') == proposed.get('trunk_vlans_list')
if not tv_check:
if proposed.get('allowed'):
command = 'switchport trunk allowed vlan {0}'
command = command.format(proposed.get('trunk_allowed_vlans'))
commands.append(command)
else:
existing_vlans = existing.get('trunk_vlans_list')
proposed_vlans = proposed.get('trunk_vlans_list')
vlans_to_add = set(proposed_vlans).difference(existing_vlans)
if vlans_to_add:
command = 'switchport trunk allowed vlan add {0}'
command = command.format(proposed.get('trunk_vlans'))
commands.append(command)
native_check = str(existing.get('native_vlan')) == str(proposed.get('native_vlan'))
if not native_check and proposed.get('native_vlan'):
command = 'switchport trunk native vlan {0}'
command = command.format(proposed.get('native_vlan'))
commands.append(command)
if commands:
commands.insert(0, 'interface ' + name)
return commands
def is_switchport_default(existing):
"""Determines if switchport has a default config based on mode
Args:
existing (dict): existing switchport configuration from Ansible mod
Returns:
boolean: True if switchport has OOB Layer 2 config, i.e.
vlan 1 and trunk all and mode is access
"""
c1 = str(existing['access_vlan']) == '1'
c2 = str(existing['native_vlan']) == '1'
c3 = existing['trunk_vlans'] == '1-4094'
c4 = existing['mode'] == 'access'
default = c1 and c2 and c3 and c4
return default
def default_switchport_config(name):
commands = []
commands.append('interface ' + name)
commands.append('switchport mode access')
commands.append('switch access vlan 1')
commands.append('switchport trunk native vlan 1')
commands.append('switchport trunk allowed vlan all')
return commands
def vlan_range_to_list(vlans):
result = []
if vlans:
for part in vlans.split(','):
if part.lower() == 'none':
break
if part:
if '-' in part:
start, stop = (int(i) for i in part.split('-'))
result.extend(range(start, stop + 1))
else:
result.append(int(part))
return sorted(result)
def get_list_of_vlans(module):
config = run_commands(module, ['show vlan'])[0]
vlans = set()
lines = config.strip().splitlines()
for line in lines:
line_parts = line.split()
if line_parts:
try:
int(line_parts[0])
except ValueError:
continue
vlans.add(line_parts[0])
return list(vlans)
def flatten_list(commands):
flat_list = []
for command in commands:
if isinstance(command, list):
flat_list.extend(command)
else:
flat_list.append(command)
return flat_list
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]
obj.append(item.copy())
else:
obj.append({
'name': module.params['name'],
'mode': module.params['mode'],
'access_vlan': module.params['access_vlan'],
'native_vlan': module.params['native_vlan'],
'trunk_vlans': module.params['trunk_vlans'],
'trunk_allowed_vlans': module.params['trunk_allowed_vlans'],
'state': module.params['state']
})
return obj
def main():
""" main entry point for module execution
"""
element_spec = dict(
name=dict(type='str', aliases=['interface']),
mode=dict(choices=['access', 'trunk'], default='access'),
access_vlan=dict(type='str'),
native_vlan=dict(type='str'),
trunk_vlans=dict(type='str'),
trunk_allowed_vlans=dict(type='str'),
state=dict(choices=['absent', 'present', 'unconfigured'],
default='present')
)
aggregate_spec = deepcopy(element_spec)
# remove default in aggregate spec, to handle common arguments
remove_default_spec(aggregate_spec)
argument_spec = dict(
aggregate=dict(type='list', elements='dict', options=aggregate_spec),
)
argument_spec.update(element_spec)
argument_spec.update(cnos_argument_spec)
module = AnsibleModule(argument_spec=argument_spec,
mutually_exclusive=[['access_vlan', 'trunk_vlans'],
['access_vlan', 'native_vlan'],
['access_vlan', 'trunk_allowed_vlans']],
supports_check_mode=True)
warnings = list()
commands = []
result = {'changed': False, 'warnings': warnings}
want = map_params_to_obj(module)
for w in want:
name = w['name']
mode = w['mode']
access_vlan = w['access_vlan']
state = w['state']
trunk_vlans = w['trunk_vlans']
native_vlan = w['native_vlan']
trunk_allowed_vlans = w['trunk_allowed_vlans']
args = dict(name=name, mode=mode, access_vlan=access_vlan,
native_vlan=native_vlan, trunk_vlans=trunk_vlans,
trunk_allowed_vlans=trunk_allowed_vlans)
proposed = dict((k, v) for k, v in args.items() if v is not None)
name = name.lower()
if mode == 'access' and state == 'present' and not access_vlan:
msg = 'access_vlan param required for mode=access && state=present'
module.fail_json(msg=msg)
if mode == 'trunk' and access_vlan:
msg = 'access_vlan param not supported when using mode=trunk'
module.fail_json(msg=msg)
if not is_switchport(name, module):
module.fail_json(msg='Ensure interface is configured to be a L2'
'\nport first before using this module. You can use'
'\nthe cnos_interface module for this.')
if interface_is_portchannel(name, module):
module.fail_json(msg='Cannot change L2 config on physical '
'\nport because it is in a portchannel. '
'\nYou should update the portchannel config.')
# existing will never be null for Eth intfs as there is always a default
existing = get_switchport(name, module)
# Safeguard check
# If there isn't an existing, something is wrong per previous comment
if not existing:
msg = 'Make sure you are using the FULL interface name'
module.fail_json(msg=msg)
if trunk_vlans or trunk_allowed_vlans:
if trunk_vlans:
trunk_vlans_list = vlan_range_to_list(trunk_vlans)
elif trunk_allowed_vlans:
trunk_vlans_list = vlan_range_to_list(trunk_allowed_vlans)
proposed['allowed'] = True
existing_trunks_list = vlan_range_to_list((existing['trunk_vlans']))
existing['trunk_vlans_list'] = existing_trunks_list
proposed['trunk_vlans_list'] = trunk_vlans_list
current_vlans = get_list_of_vlans(module)
if state == 'present':
if access_vlan and access_vlan not in current_vlans:
module.fail_json(msg='You are trying to configure a VLAN'
' on an interface that\ndoes not exist on the '
' switch yet!', vlan=access_vlan)
elif native_vlan and native_vlan not in current_vlans:
module.fail_json(msg='You are trying to configure a VLAN on'
' an interface that\ndoes not exist on the '
' switch yet!', vlan=native_vlan)
else:
command = get_switchport_config_commands(name, existing,
proposed, module)
commands.append(command)
elif state == 'unconfigured':
is_default = is_switchport_default(existing)
if not is_default:
command = default_switchport_config(name)
commands.append(command)
elif state == 'absent':
command = remove_switchport_config_commands(name, existing,
proposed, module)
commands.append(command)
if trunk_vlans or trunk_allowed_vlans:
existing.pop('trunk_vlans_list')
proposed.pop('trunk_vlans_list')
cmds = flatten_list(commands)
if cmds:
if module.check_mode:
module.exit_json(changed=True, commands=cmds)
else:
result['changed'] = True
load_config(module, cmds)
if 'configure' in cmds:
cmds.pop(0)
result['commands'] = cmds
module.exit_json(**result)
if __name__ == '__main__':
main()

View file

@ -0,0 +1,461 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
#
# Copyright (C) 2019 Lenovo, Inc.
# (c) 2019, Ansible by Red Hat, inc
# This file is part of Ansible
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
#
# Module to work on Link Aggregation with Lenovo Switches
# Lenovo Networking
#
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = '''
---
module: cnos_l3_interface
author: "Anil Kumar Muraleedharan (@amuraleedhar)"
short_description: Manage Layer-3 interfaces on Lenovo CNOS network devices.
description:
- This module provides declarative management of Layer-3 interfaces
on CNOS network devices.
notes:
- Tested against CNOS 10.8.1
options:
name:
description:
- Name of the Layer-3 interface to be configured eg. Ethernet1/2
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. 10.241.107.1/24
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
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.
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']
provider:
description:
- B(Deprecated)
- "Starting with Ansible 2.5 we recommend using
C(connection: network_cli)."
- For more information please see the
L(CNOS Platform Options guide, ../network/user_guide/platform_cnos.html).
- HORIZONTALLINE
- A dict object containing connection details.
suboptions:
host:
description:
- Specifies the DNS host name or address for connecting to the remote
device over the specified transport. The value of host is used as
the destination address for the transport.
required: true
port:
description:
- Specifies the port to use when building the connection to the
remote device.
default: 22
username:
description:
- Configures the username to use to authenticate the connection to
the remote device. This value is used to authenticate
the SSH session. If the value is not specified in the task, the
value of environment variable C(ANSIBLE_NET_USERNAME) will be used
instead.
password:
description:
- Specifies the password to use to authenticate the connection to
the remote device. This value is used to authenticate
the SSH session. If the value is not specified in the task, the
value of environment variable C(ANSIBLE_NET_PASSWORD) will be used
instead.
timeout:
description:
- Specifies the timeout in seconds for communicating with the network
device for either connecting or sending commands. If the timeout
is exceeded before the operation is completed, the module will
error.
default: 10
ssh_keyfile:
description:
- Specifies the SSH key to use to authenticate the connection to
the remote device. This value is the path to the
key used to authenticate the SSH session. If the value is not
specified in the task, the value of environment variable
C(ANSIBLE_NET_SSH_KEYFILE)will be used instead.
authorize:
description:
- Instructs the module to enter privileged mode on the remote device
before sending any commands. If not specified, the device will
attempt to execute all commands in non-privileged mode. If the
value is not specified in the task, the value of environment
variable C(ANSIBLE_NET_AUTHORIZE) will be used instead.
type: bool
default: 'no'
auth_pass:
description:
- Specifies the password to use if required to enter privileged mode
on the remote device. If I(authorize) is false, then this argument
does nothing. If the value is not specified in the task, the value
of environment variable C(ANSIBLE_NET_AUTH_PASS) will be used
instead.
'''
EXAMPLES = """
- name: Remove Ethernet1/33 IPv4 and IPv6 address
cnos_l3_interface:
name: Ethernet1/33
state: absent
- name: Set Ethernet1/33 IPv4 address
cnos_l3_interface:
name: Ethernet1/33
ipv4: 10.241.107.1/24
- name: Set Ethernet1/33 IPv6 address
cnos_l3_interface:
name: Ethernet1/33
ipv6: "fd5d:12c9:2201:1::1/64"
- name: Set Ethernet1/33 in dhcp
cnos_l3_interface:
name: Ethernet1/33
ipv4: dhcp
ipv6: dhcp
- name: Set interface Vlan1 (SVI) IPv4 address
cnos_l3_interface:
name: Vlan1
ipv4: 192.168.0.5/24
- name: Set IP addresses on aggregate
cnos_l3_interface:
aggregate:
- { name: Ethernet1/33, ipv4: 10.241.107.1/24 }
- { name: Ethernet1/44, ipv4: 10.240.106.1/24,
ipv6: "fd5d:12c9:2201:1::1/64" }
- name: Remove IP addresses on aggregate
cnos_l3_interface:
aggregate:
- { name: Ethernet1/33, ipv4: 10.241.107.1/24 }
- { name: Ethernet1/44, ipv4: 10.240.106.1/24,
ipv6: "fd5d:12c9:2201:1::1/64" }
state: absent
"""
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 Ethernet1/33
- ip address 10.241.107.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
from ansible_collections.community.general.plugins.module_utils.network.cnos.cnos import get_config, load_config
from ansible_collections.community.general.plugins.module_utils.network.cnos.cnos import cnos_argument_spec
from ansible_collections.community.general.plugins.module_utils.network.cnos.cnos import run_commands
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
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import 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)
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:
# validate the param value (if validator func exists)
validator = globals().get('validate_%s' % key)
if callable(validator):
validator(param.get(key), module)
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'].lower() == name.lower():
return o
return None
def get_interface_type(interface):
intf_type = 'unknown'
if interface.upper()[:2] in ('ET', 'GI', 'FA', 'TE', 'FO', 'HU', 'TWE'):
intf_type = 'ethernet'
elif interface.upper().startswith('VL'):
intf_type = 'svi'
elif interface.upper().startswith('LO'):
intf_type = 'loopback'
elif interface.upper()[:2] in ('MG', 'MA'):
intf_type = 'management'
elif interface.upper().startswith('PO'):
intf_type = 'portchannel'
elif interface.upper().startswith('NV'):
intf_type = 'nve'
return intf_type
def is_switchport(name, module):
intf_type = get_interface_type(name)
if intf_type in ('ethernet', 'portchannel'):
config = run_commands(module,
['show interface {0} switchport'.format(name)])[0]
match = re.search(r'Switchport : enabled', config)
return bool(match)
return False
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']
interface = 'interface ' + name
commands.append(interface)
obj_in_have = search_obj_in_list(name, have)
if state == 'absent' and obj_in_have:
if obj_in_have['ipv4']:
if ipv4:
address = ipv4.split('/')
if len(address) == 2:
ipv4 = '{0} {1}'.format(
address[0], to_netmask(address[1]))
commands.append('no ip address %s' % ipv4)
else:
commands.append('no ip address')
if obj_in_have['ipv6']:
if ipv6:
commands.append('no ipv6 address %s' % ipv6)
else:
commands.append('no ipv6 address')
if 'dhcp' in obj_in_have['ipv6']:
commands.append('no ipv6 address dhcp')
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' % ipv4)
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 %s' % ipv6)
if commands[-1] == interface:
commands.pop(-1)
return commands
def map_config_to_obj(module):
config = get_config(module)
configobj = NetworkConfig(indent=1, contents=config)
match = re.findall(r'^interface (\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:
# eg. 192.168.2.10 255.255.255.0 -> 192.168.2.10/24
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_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']
})
validate_param_values(module, obj)
return obj
def main():
""" main entry point for module execution
"""
element_spec = dict(
name=dict(),
ipv4=dict(),
ipv6=dict(),
state=dict(default='present',
choices=['present', 'absent'])
)
aggregate_spec = deepcopy(element_spec)
aggregate_spec['name'] = dict(required=True)
# remove default in aggregate spec, to handle common arguments
remove_default_spec(aggregate_spec)
argument_spec = dict(
aggregate=dict(type='list', elements='dict', options=aggregate_spec),
)
argument_spec.update(element_spec)
argument_spec.update(cnos_argument_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 = {'changed': False}
want = map_params_to_obj(module)
for w in want:
name = w['name']
name = name.lower()
if is_switchport(name, module):
module.fail_json(msg='Ensure interface is configured to be a L3'
'\nport first before using this module. You can use'
'\nthe cnos_interface module for this.')
have = map_config_to_obj(module)
commands = map_obj_to_commands((want, have), module)
result['commands'] = commands
if commands:
if not module.check_mode:
resp = load_config(module, commands)
if resp is not None:
warnings.extend((out for out in resp if out))
result['changed'] = True
if warnings:
result['warnings'] = warnings
if 'overlaps with address configured on' in warnings[0]:
result['failed'] = True
result['msg'] = warnings[0]
if 'Cannot set overlapping address' in warnings[0]:
result['failed'] = True
result['msg'] = warnings[0]
module.exit_json(**result)
if __name__ == '__main__':
main()

View file

@ -0,0 +1,391 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
#
# Copyright (C) 2017 Lenovo, Inc.
# (c) 2017, Ansible by Red Hat, inc
# This file is part of Ansible
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
#
# Module to work on Link Aggregation with Lenovo Switches
# Lenovo Networking
#
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = '''
---
module: cnos_linkagg
author: "Anil Kumar Muraleedharan (@auraleedhar)"
short_description: Manage link aggregation groups on Lenovo CNOS devices
description:
- This module provides declarative management of link aggregation groups
on Lenovo CNOS network devices.
notes:
- Tested against CNOS 10.8.1
options:
group:
description:
- Channel-group number for the port-channel
Link aggregation group. Range 1-255.
mode:
description:
- Mode of the link aggregation group.
choices: ['active', 'on', 'passive']
members:
description:
- List of members of the link aggregation group.
aggregate:
description: List of link aggregation definitions.
state:
description:
- State of the link aggregation group.
default: present
choices: ['present', 'absent']
purge:
description:
- Purge links not defined in the I(aggregate) parameter.
type: bool
default: no
provider:
description:
- B(Deprecated)
- "Starting with Ansible 2.5 we recommend using C(connection: network_cli)."
- For more information please see the L(CNOS Platform Options guide, ../network/user_guide/platform_cnos.html).
- HORIZONTALLINE
- A dict object containing connection details.
suboptions:
host:
description:
- Specifies the DNS host name or address for connecting to the remote
device over the specified transport. The value of host is used as
the destination address for the transport.
required: true
port:
description:
- Specifies the port to use when building the connection to the remote device.
default: 22
username:
description:
- Configures the username to use to authenticate the connection to
the remote device. This value is used to authenticate
the SSH session. If the value is not specified in the task, the
value of environment variable C(ANSIBLE_NET_USERNAME) will be used instead.
password:
description:
- Specifies the password to use to authenticate the connection to
the remote device. This value is used to authenticate
the SSH session. If the value is not specified in the task, the
value of environment variable C(ANSIBLE_NET_PASSWORD) will be used instead.
timeout:
description:
- Specifies the timeout in seconds for communicating with the network device
for either connecting or sending commands. If the timeout is
exceeded before the operation is completed, the module will error.
default: 10
ssh_keyfile:
description:
- Specifies the SSH key to use to authenticate the connection to
the remote device. This value is the path to the
key used to authenticate the SSH session. If the value is not specified
in the task, the value of environment variable C(ANSIBLE_NET_SSH_KEYFILE)
will be used instead.
authorize:
description:
- Instructs the module to enter privileged mode on the remote device
before sending any commands. If not specified, the device will
attempt to execute all commands in non-privileged mode. If the value
is not specified in the task, the value of environment variable
C(ANSIBLE_NET_AUTHORIZE) will be used instead.
type: bool
default: 'no'
auth_pass:
description:
- Specifies the password to use if required to enter privileged mode
on the remote device. If I(authorize) is false, then this argument
does nothing. If the value is not specified in the task, the value of
environment variable C(ANSIBLE_NET_AUTH_PASS) will be used instead.
'''
EXAMPLES = """
- name: create link aggregation group
cnos_linkagg:
group: 10
state: present
- name: delete link aggregation group
cnos_linkagg:
group: 10
state: absent
- name: set link aggregation group to members
cnos_linkagg:
group: 200
mode: active
members:
- Ethernet1/33
- Ethernet1/44
- name: remove link aggregation group from GigabitEthernet0/0
cnos_linkagg:
group: 200
mode: active
members:
- Ethernet1/33
- name: Create aggregate of linkagg definitions
cnos_linkagg:
aggregate:
- { group: 3, mode: on, members: [Ethernet1/33] }
- { group: 100, mode: passive, members: [Ethernet1/44] }
"""
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 port-channel 30
- interface Ethernet1/33
- channel-group 30 mode on
- no interface port-channel 30
"""
import re
from copy import deepcopy
from ansible.module_utils.basic import AnsibleModule
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
from ansible_collections.community.general.plugins.module_utils.network.cnos.cnos import get_config, load_config
from ansible_collections.community.general.plugins.module_utils.network.cnos.cnos import cnos_argument_spec
def search_obj_in_list(group, lst):
for o in lst:
if o['group'] == group:
return o
def map_obj_to_commands(updates, module):
commands = list()
want, have = updates
purge = module.params['purge']
for w in want:
group = w['group']
mode = w['mode']
members = w.get('members') or []
state = w['state']
del w['state']
obj_in_have = search_obj_in_list(group, have)
if state == 'absent':
if obj_in_have:
commands.append('no interface port-channel {0}'.format(group))
elif state == 'present':
cmd = ['interface port-channel {0}'.format(group),
'exit']
if not obj_in_have:
if not group:
module.fail_json(msg='group is a required option')
commands.extend(cmd)
if members:
for m in members:
commands.append('interface {0}'.format(m))
commands.append('channel-group {0} mode {1}'.format(group, mode))
else:
if members:
if 'members' not in obj_in_have.keys():
for m in members:
commands.extend(cmd)
commands.append('interface {0}'.format(m))
commands.append('channel-group {0} mode {1}'.format(group, mode))
elif set(members) != set(obj_in_have['members']):
missing_members = list(set(members) - set(obj_in_have['members']))
for m in missing_members:
commands.extend(cmd)
commands.append('interface {0}'.format(m))
commands.append('channel-group {0} mode {1}'.format(group, mode))
superfluous_members = list(set(obj_in_have['members']) - set(members))
for m in superfluous_members:
commands.extend(cmd)
commands.append('interface {0}'.format(m))
commands.append('no channel-group')
if purge:
for h in have:
obj_in_want = search_obj_in_list(h['group'], want)
if not obj_in_want:
commands.append('no interface port-channel {0}'.format(h['group']))
return commands
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']
})
return obj
def parse_mode(module, config, group, member):
mode = None
netcfg = CustomNetworkConfig(indent=1, contents=config)
parents = ['interface {0}'.format(member)]
body = netcfg.get_section(parents)
match_int = re.findall(r'interface {0}\n'.format(member), body, re.M)
if match_int:
match = re.search(r'channel-group {0} mode (\S+)'.format(group),
body, re.M)
if match:
mode = match.group(1)
return mode
def parse_members(module, config, group):
members = []
for line in config.strip().split('!'):
l = line.strip()
if l.startswith('interface'):
match_group = re.findall(r'channel-group {0} mode'.format(group), l, re.M)
if match_group:
match = re.search(r'interface (\S+)', l, re.M)
if match:
members.append(match.group(1))
return members
def get_channel(module, config, group):
match = re.findall(r'^interface (\S+)', config, re.M)
if not match:
return {}
channel = {}
for item in set(match):
member = item
channel['mode'] = parse_mode(module, config, group, member)
channel['members'] = parse_members(module, config, group)
return channel
def map_config_to_obj(module):
objs = list()
config = get_config(module)
for line in config.split('\n'):
l = line.strip()
match = re.search(r'interface port-channel(\S+)', l, re.M)
if match:
obj = {}
group = match.group(1)
obj['group'] = group
obj.update(get_channel(module, config, group))
objs.append(obj)
return objs
def main():
""" main entry point for module execution
"""
element_spec = dict(
group=dict(type='int'),
mode=dict(choices=['active', 'on', 'passive']),
members=dict(type='list'),
state=dict(default='present',
choices=['present', 'absent'])
)
aggregate_spec = deepcopy(element_spec)
aggregate_spec['group'] = dict(required=True)
required_one_of = [['group', 'aggregate']]
required_together = [['members', 'mode']]
mutually_exclusive = [['group', 'aggregate']]
# remove default in aggregate spec, to handle common arguments
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)
argument_spec.update(cnos_argument_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}
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,139 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
#
# Copyright (C) 2019 Lenovo.
# (c) 2017, Ansible by Red Hat, inc
# This file is part of Ansible
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
#
# Module to work on Link Aggregation with Lenovo Switches
# Lenovo Networking
#
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = '''
---
module: cnos_lldp
author: "Anil Kumar Muraleedharan (@amuraleedhar)"
short_description: Manage LLDP configuration on Lenovo CNOS network devices.
description:
- This module provides declarative management of LLDP service
on Lenovc CNOS network devices.
notes:
- Tested against CNOS 10.9.1
options:
state:
description:
- State of the LLDP configuration. If value is I(present) lldp will be
enabled else if it is I(absent) it will be disabled.
default: present
choices: ['present', 'absent']
'''
EXAMPLES = """
- name: Enable LLDP service
cnos_lldp:
state: present
- name: Disable LLDP service
cnos_lldp:
state: absent
"""
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 timer 1024
- lldp trap-interval 330
"""
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.community.general.plugins.module_utils.network.cnos.cnos import get_config, load_config
from ansible_collections.community.general.plugins.module_utils.network.cnos.cnos import cnos_argument_spec
from ansible_collections.community.general.plugins.module_utils.network.cnos.cnos import debugOutput, run_commands
from ansible.module_utils.connection import exec_command
def get_ethernet_range(module):
output = run_commands(module, ['show interface brief'])[0].split('\n')
maxport = None
last_interface = None
for line in output:
if line.startswith('Ethernet1/'):
last_interface = line.split(' ')[0]
if last_interface is not None:
eths = last_interface.split('/')
maxport = eths[1]
return maxport
def main():
""" main entry point for module execution
"""
argument_spec = dict(
state=dict(default='present',
choices=['present', 'absent'])
)
module = AnsibleModule(argument_spec=argument_spec,
supports_check_mode=True)
warnings = list()
result = {'changed': False}
if warnings:
result['warnings'] = warnings
maxport = get_ethernet_range(module)
commands = []
prime_cmd = 'interface ethernet 1/1-' + maxport
if module.params['state'] == 'absent':
commands.append(prime_cmd)
commands.append('no lldp receive')
commands.append('no lldp transmit')
commands.append('exit')
commands.append('interface mgmt 0')
commands.append('no lldp receive')
commands.append('no lldp transmit')
commands.append('exit')
elif module.params['state'] == 'present':
commands.append(prime_cmd)
commands.append('lldp receive')
commands.append('lldp transmit')
commands.append('exit')
commands.append('interface mgmt 0')
commands.append('lldp receive')
commands.append('lldp transmit')
commands.append('exit')
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,425 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
#
# Copyright (C) 2019 Lenovo, Inc.
# (c) 2017, Ansible by Red Hat, inc
# This file is part of Ansible
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
#
# Module to work on Link Aggregation with Lenovo Switches
# Lenovo Networking
#
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = '''
---
module: cnos_logging
author: "Anil Kumar Muraleedharan (@amuraleedhar)"
short_description: Manage logging on network devices
description:
- This module provides declarative management of logging
on Cisco Cnos devices.
notes:
- Tested against CNOS 10.9.1
options:
dest:
description:
- Destination of the logs. Lenovo uses the term server instead of host in
its CLI.
choices: ['server', 'console', 'monitor', 'logfile']
name:
description:
- If value of C(dest) is I(file) it indicates file-name
and for I(server) indicates the server name to be notified.
size:
description:
- Size of buffer. The acceptable value is in range from 4096 to
4294967295 bytes.
default: 10485760
facility:
description:
- Set logging facility. This is applicable only for server logging
level:
description:
- Set logging severity levels. 0-emerg;1-alert;2-crit;3-err;4-warn;
5-notif;6-inform;7-debug
default: 5
aggregate:
description: List of logging definitions.
state:
description:
- State of the logging configuration.
default: present
choices: ['present', 'absent']
'''
EXAMPLES = """
- name: configure server logging
cnos_logging:
dest: server
name: 10.241.107.224
facility: local7
state: present
- name: remove server logging configuration
cnos_logging:
dest: server
name: 10.241.107.224
state: absent
- name: configure console logging level and facility
cnos_logging:
dest: console
level: 7
state: present
- name: configure buffer size
cnos_logging:
dest: logfile
level: 5
name: testfile
size: 5000
- name: Configure logging using aggregate
cnos_logging:
aggregate:
- { dest: console, level: 6 }
- { dest: logfile, size: 9000 }
- name: remove logging using aggregate
cnos_logging:
aggregate:
- { dest: console, level: 6 }
- { dest: logfile, name: anil, size: 9000 }
state: absent
"""
RETURN = """
commands:
description: The list of configuration mode commands to send to the device
returned: always
type: list
sample:
- logging console 7
- logging server 10.241.107.224
"""
import re
from copy import deepcopy
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import validate_ip_address
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import remove_default_spec
from ansible_collections.community.general.plugins.module_utils.network.cnos.cnos import get_config, load_config
from ansible_collections.community.general.plugins.module_utils.network.cnos.cnos import get_capabilities
from ansible_collections.community.general.plugins.module_utils.network.cnos.cnos import check_args
from ansible_collections.community.general.plugins.module_utils.network.cnos.cnos import cnos_argument_spec
def validate_size(value, module):
if value:
if not int(4096) <= int(value) <= int(4294967295):
module.fail_json(msg='size must be between 4096 and 4294967295')
else:
return value
def map_obj_to_commands(updates, module):
dest_group = ('console', 'monitor', 'logfile', 'server')
commands = list()
want, have = updates
for w in want:
dest = w['dest']
name = w['name']
size = w['size']
facility = w['facility']
level = w['level']
state = w['state']
del w['state']
if state == 'absent':
if dest:
if dest == 'server':
commands.append('no logging server {0}'.format(name))
elif dest in dest_group:
commands.append('no logging {0}'.format(dest))
else:
module.fail_json(msg='dest must be among console, monitor, logfile, server')
if state == 'present' and w not in have:
if dest == 'server':
cmd_str = 'logging server {0}'.format(name)
if level is not None and level > 0 and level < 8:
cmd_str = cmd_str + ' ' + level
if facility is not None:
cmd_str = cmd_str + ' facility ' + facility
commands.append(cmd_str)
elif dest == 'logfile' and size:
present = False
for entry in have:
if entry['dest'] == 'logfile' and entry['size'] == size and entry['level'] == level:
present = True
if not present:
cmd_str = 'logging logfile '
if name is not None:
cmd_str = cmd_str + name
if level and level != '7':
cmd_str = cmd_str + ' ' + level
else:
cmd_str = cmd_str + ' 7'
if size is not None:
cmd_str = cmd_str + ' size ' + size
commands.append(cmd_str)
else:
module.fail_json(msg='Name of the logfile is a mandatory parameter')
else:
if dest:
dest_cmd = 'logging {0}'.format(dest)
if level:
dest_cmd += ' {0}'.format(level)
commands.append(dest_cmd)
return commands
def parse_facility(line, dest):
facility = None
if dest == 'server':
result = line.split()
i = 0
for x in result:
if x == 'facility':
return result[i + 1]
i = i + 1
return facility
def parse_size(line, dest):
size = None
if dest == 'logfile':
if 'logging logfile' in line:
result = line.split()
i = 0
for x in result:
if x == 'size':
return result[i + 1]
i = i + 1
return '10485760'
return size
def parse_name(line, dest):
name = None
if dest == 'server':
if 'logging server' in line:
result = line.split()
i = 0
for x in result:
if x == 'server':
name = result[i + 1]
elif dest == 'logfile':
if 'logging logfile' in line:
result = line.split()
i = 0
for x in result:
if x == 'logfile':
name = result[i + 1]
else:
name = None
return name
def parse_level(line, dest):
level_group = ('0', '1', '2', '3', '4', '5', '6', '7')
level = '7'
if dest == 'server':
if 'logging server' in line:
result = line.split()
if(len(result) > 3):
if result[3].isdigit():
level = result[3]
else:
if dest == 'logfile':
if 'logging logfile' in line:
result = line.split()
if result[3].isdigit():
level = result[3]
else:
match = re.search(r'logging {0} (\S+)'.format(dest), line, re.M)
return level
def map_config_to_obj(module):
obj = []
dest_group = ('console', 'server', 'monitor', 'logfile')
data = get_config(module, flags=['| include logging'])
index = 0
for line in data.split('\n'):
logs = line.split()
index = len(logs)
if index == 0 or index == 1:
continue
if logs[0] != 'logging':
continue
if logs[1] == 'monitor' or logs[1] == 'console':
obj.append({'dest': logs[1], 'level': logs[2]})
elif logs[1] == 'logfile':
level = '5'
if index > 3 and logs[3].isdigit():
level = logs[3]
size = '10485760'
if len(logs) > 4:
size = logs[5]
obj.append({'dest': logs[1], 'name': logs[2], 'size': size, 'level': level})
elif logs[1] == 'server':
level = '5'
facility = None
if index > 3 and logs[3].isdigit():
level = logs[3]
if index > 3 and logs[3] == 'facility':
facility = logs[4]
if index > 4 and logs[4] == 'facility':
facility = logs[5]
obj.append({'dest': logs[1], 'name': logs[2], 'facility': facility, 'level': level})
else:
continue
return obj
def map_params_to_obj(module, required_if=None):
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]
module._check_required_if(required_if, item)
d = item.copy()
if d['dest'] != 'server' and d['dest'] != 'logfile':
d['name'] = None
if d['dest'] == 'logfile':
if 'size' in d:
d['size'] = str(validate_size(d['size'], module))
elif 'size' not in d:
d['size'] = str(10485760)
else:
pass
if d['dest'] != 'logfile':
d['size'] = None
obj.append(d)
else:
if module.params['dest'] != 'server' and module.params['dest'] != 'logfile':
module.params['name'] = None
if module.params['dest'] == 'logfile':
if not module.params['size']:
module.params['size'] = str(10485760)
else:
module.params['size'] = None
if module.params['size'] is None:
obj.append({
'dest': module.params['dest'],
'name': module.params['name'],
'size': module.params['size'],
'facility': module.params['facility'],
'level': module.params['level'],
'state': module.params['state']
})
else:
obj.append({
'dest': module.params['dest'],
'name': module.params['name'],
'size': str(validate_size(module.params['size'], module)),
'facility': module.params['facility'],
'level': module.params['level'],
'state': module.params['state']
})
return obj
def main():
""" main entry point for module execution
"""
element_spec = dict(
dest=dict(type='str',
choices=['server', 'console', 'monitor', 'logfile']),
name=dict(type='str'),
size=dict(type='int', default=10485760),
facility=dict(type='str'),
level=dict(type='str', default='5'),
state=dict(default='present', choices=['present', 'absent']),
)
aggregate_spec = deepcopy(element_spec)
# remove default in aggregate spec, to handle common arguments
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', 'server', ['name'])]
module = AnsibleModule(argument_spec=argument_spec,
required_if=required_if,
supports_check_mode=True)
warnings = list()
check_args(module, warnings)
result = {'changed': False}
if warnings:
result['warnings'] = warnings
want = map_params_to_obj(module, required_if=required_if)
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,114 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
#
# Copyright (C) 2017 Lenovo, Inc.
#
# This file is part of Ansible
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
#
# Module to reload Lenovo Switches
# Lenovo Networking
#
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = '''
---
module: cnos_reload
author: "Anil Kumar Muraleedharan (@amuraleedhar)"
short_description: Perform switch restart on devices running Lenovo CNOS
description:
- This module allows you to restart the switch using the current startup
configuration. The module is usually invoked after the running
configuration has been saved over the startup configuration.
This module uses SSH to manage network device configuration.
The results of the operation can be viewed in results directory.
extends_documentation_fragment:
- community.general.cnos
options: {}
'''
EXAMPLES = '''
Tasks : The following are examples of using the module cnos_reload. These are
written in the main.yml file of the tasks directory.
---
- name: Test Reload
cnos_reload:
deviceType: "{{ hostvars[inventory_hostname]['deviceType'] }}"
outputfile: "./results/test_reload_{{ inventory_hostname }}_output.txt"
'''
RETURN = '''
msg:
description: Success or failure message
returned: always
type: str
sample: "Device is Reloading. Please wait..."
'''
import sys
import time
import socket
import array
import json
import time
import re
try:
from ansible_collections.community.general.plugins.module_utils.network.cnos import cnos
HAS_LIB = True
except Exception:
HAS_LIB = False
from ansible.module_utils.basic import AnsibleModule
from collections import defaultdict
def main():
module = AnsibleModule(
argument_spec=dict(
outputfile=dict(required=True),
host=dict(required=False),
username=dict(required=False),
password=dict(required=False, no_log=True),
enablePassword=dict(required=False, no_log=True),
deviceType=dict(required=True),),
supports_check_mode=False)
command = 'reload'
outputfile = module.params['outputfile']
output = ''
cmd = [{'command': command, 'prompt': 'reboot system? (y/n): ',
'answer': 'y'}]
output = output + str(cnos.run_cnos_commands(module, cmd))
# Save it into the file
file = open(outputfile, "a")
file.write(output)
file.close()
errorMsg = cnos.checkOutputForError(output)
if(errorMsg in "Device Response Timed out"):
module.exit_json(changed=True,
msg="Device is Reloading. Please wait...")
else:
module.fail_json(msg=errorMsg)
if __name__ == '__main__':
main()

View file

@ -0,0 +1,285 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
#
# Copyright (C) 2017 Lenovo, Inc.
#
# This file is part of Ansible
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
#
# Module to Rollback Config back to Lenovo Switches
#
# Lenovo Networking
#
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = '''
---
module: cnos_rollback
author: "Anil Kumar Muraleedharan (@amuraleedhar)"
short_description: Roll back the running or startup configuration from a remote
server on devices running Lenovo CNOS
description:
- This module allows you to work with switch configurations. It provides a
way to roll back configurations of a switch from a remote server. This is
achieved by using startup or running configurations of the target device
that were previously backed up to a remote server using FTP, SFTP, TFTP,
or SCP. The first step is to create a directory from where the remote
server can be reached. The next step is to provide the full file path of
he backup configuration's location. Authentication details required by the
remote server must be provided as well.
By default, this method overwrites the switch's configuration file with
the newly downloaded file. This module uses SSH to manage network device
configuration. The results of the operation will be placed in a directory
named 'results' that must be created by the user in their local directory
to where the playbook is run.
extends_documentation_fragment:
- community.general.cnos
options:
configType:
description:
- This refers to the type of configuration which will be used for
the rolling back process. The choices are the running or startup
configurations. There is no default value, so it will result
in an error if the input is incorrect.
required: Yes
default: Null
choices: [running-config, startup-config]
protocol:
description:
- This refers to the protocol used by the network device to
interact with the remote server from where to download the backup
configuration. The choices are FTP, SFTP, TFTP, or SCP. Any other
protocols will result in error. If this parameter is not
specified, there is no default value to be used.
required: Yes
default: Null
choices: [SFTP, SCP, FTP, TFTP]
rcserverip:
description:
- This specifies the IP Address of the remote server from where the
backup configuration will be downloaded.
required: Yes
default: Null
rcpath:
description:
- This specifies the full file path of the configuration file
located on the remote server. In case the relative path is used as
the variable value, the root folder for the user of the server
needs to be specified.
required: Yes
default: Null
serverusername:
description:
- Specify username for the server relating to the protocol used.
required: Yes
default: Null
serverpassword:
description:
- Specify password for the server relating to the protocol used.
required: Yes
default: Null
'''
EXAMPLES = '''
Tasks : The following are examples of using the module cnos_rollback.
These are written in the main.yml file of the tasks directory.
---
- name: Test Rollback of config - Running config
cnos_rolback:
deviceType: "{{ hostvars[inventory_hostname]['deviceType'] }}"
outputfile: "./results/test_rollback_{{ inventory_hostname }}_output.txt"
configType: running-config
protocol: "sftp"
serverip: "10.241.106.118"
rcpath: "/root/cnos/G8272-running-config.txt"
serverusername: "root"
serverpassword: "root123"
- name: Test Rollback of config - Startup config
cnos_rolback:
deviceType: "{{ hostvars[inventory_hostname]['deviceType'] }}"
outputfile: "./results/test_rollback_{{ inventory_hostname }}_output.txt"
configType: startup-config
protocol: "sftp"
serverip: "10.241.106.118"
rcpath: "/root/cnos/G8272-startup-config.txt"
serverusername: "root"
serverpassword: "root123"
- name: Test Rollback of config - Running config - TFTP
cnos_rolback:
deviceType: "{{ hostvars[inventory_hostname]['deviceType'] }}"
outputfile: "./results/test_rollback_{{ inventory_hostname }}_output.txt"
configType: running-config
protocol: "tftp"
serverip: "10.241.106.118"
rcpath: "/anil/G8272-running-config.txt"
serverusername: "root"
serverpassword: "root123"
- name: Test Rollback of config - Startup config - TFTP
cnos_rolback:
deviceType: "{{ hostvars[inventory_hostname]['deviceType'] }}"
outputfile: "./results/test_rollback_{{ inventory_hostname }}_output.txt"
configType: startup-config
protocol: "tftp"
serverip: "10.241.106.118"
rcpath: "/anil/G8272-startup-config.txt"
serverusername: "root"
serverpassword: "root123"
'''
RETURN = '''
msg:
description: Success or failure message
returned: always
type: str
sample: "Config file transferred to Device"
'''
import sys
import time
import socket
import array
import json
import time
import re
import os
try:
from ansible_collections.community.general.plugins.module_utils.network.cnos import cnos
HAS_LIB = True
except Exception:
HAS_LIB = False
from ansible.module_utils.basic import AnsibleModule
from collections import defaultdict
# Utility Method to rollback the running config or start up config
# This method supports only SCP or SFTP or FTP or TFTP
def doConfigRollBack(module, prompt, answer):
host = module.params['host']
server = module.params['serverip']
username = module.params['serverusername']
password = module.params['serverpassword']
protocol = module.params['protocol'].lower()
rcPath = module.params['rcpath']
configType = module.params['configType']
confPath = rcPath
retVal = ''
command = "copy " + protocol + " " + protocol + "://"
command = command + username + "@" + server + "/" + confPath
command = command + " " + configType + " vrf management\n"
cnos.debugOutput(command + "\n")
# cnos.checkForFirstTimeAccess(module, command, 'yes/no', 'yes')
cmd = []
if(protocol == "scp"):
scp_cmd1 = [{'command': command, 'prompt': 'timeout:', 'answer': '0'}]
scp_cmd2 = [{'command': '\n', 'prompt': 'Password:',
'answer': password}]
cmd.extend(scp_cmd1)
cmd.extend(scp_cmd2)
if(configType == 'startup-config'):
scp_cmd3 = [{'command': 'y', 'prompt': None, 'answer': None}]
cmd.extend(scp_cmd3)
retVal = retVal + str(cnos.run_cnos_commands(module, cmd))
elif(protocol == "sftp"):
sftp_cmd = [{'command': command, 'prompt': 'Password:',
'answer': password}]
cmd.extend(sftp_cmd)
# cnos.debugOutput(configType + "\n")
if(configType == 'startup-config'):
sftp_cmd2 = [{'command': 'y', 'prompt': None, 'answer': None}]
cmd.extend(sftp_cmd2)
retVal = retVal + str(cnos.run_cnos_commands(module, cmd))
elif(protocol == "ftp"):
ftp_cmd = [{'command': command, 'prompt': 'Password:',
'answer': password}]
cmd.extend(ftp_cmd)
if(configType == 'startup-config'):
ftp_cmd2 = [{'command': 'y', 'prompt': None, 'answer': None}]
cmd.extend(ftp_cmd2)
retVal = retVal + str(cnos.run_cnos_commands(module, cmd))
elif(protocol == "tftp"):
command = "copy " + protocol + " " + protocol
command = command + "://" + server + "/" + confPath
command = command + " " + configType + " vrf management\n"
cnos.debugOutput(command)
tftp_cmd = [{'command': command, 'prompt': None, 'answer': None}]
cmd.extend(tftp_cmd)
if(configType == 'startup-config'):
tftp_cmd2 = [{'command': 'y', 'prompt': None, 'answer': None}]
cmd.extend(tftp_cmd2)
retVal = retVal + str(cnos.run_cnos_commands(module, cmd))
else:
return "Error-110"
return retVal
# EOM
def main():
module = AnsibleModule(
argument_spec=dict(
outputfile=dict(required=True),
host=dict(required=False),
username=dict(required=False),
password=dict(required=False, no_log=True),
enablePassword=dict(required=False, no_log=True),
deviceType=dict(required=True),
configType=dict(required=True),
protocol=dict(required=True),
serverip=dict(required=True),
rcpath=dict(required=True),
serverusername=dict(required=False),
serverpassword=dict(required=False, no_log=True),),
supports_check_mode=False)
outputfile = module.params['outputfile']
protocol = module.params['protocol'].lower()
output = ''
if protocol in ('tftp', 'ftp', 'sftp', 'scp'):
transfer_status = doConfigRollBack(module, None, None)
else:
transfer_status = 'Invalid Protocol option'
output = output + "\n Config Transfer status \n" + transfer_status
# Save it into the file
if '/' in outputfile:
path = outputfile.rsplit('/', 1)
# cnos.debugOutput(path[0])
if not os.path.exists(path[0]):
os.makedirs(path[0])
file = open(outputfile, "a")
file.write(output)
file.close()
# need to add logic to check when changes occur or not
errorMsg = cnos.checkOutputForError(output)
if(errorMsg is None):
module.exit_json(changed=True, msg="Config file transferred to Device")
else:
module.fail_json(msg=errorMsg)
if __name__ == '__main__':
main()

View file

@ -0,0 +1,116 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
#
# Copyright (C) 2017 Lenovo, Inc.
#
# This file is part of Ansible
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
#
# Module to save running config to start up config to Lenovo Switches
# Lenovo Networking
#
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = '''
---
module: cnos_save
author: "Anil Kumar Muraleedharan (@amuraleedhar)"
short_description: Save the running configuration as the startup configuration
on devices running Lenovo CNOS
description:
- This module allows you to copy the running configuration of a switch over
its startup configuration. It is recommended to use this module shortly
after any major configuration changes so they persist after a switch
restart. This module uses SSH to manage network device configuration.
The results of the operation will be placed in a directory named 'results'
that must be created by the user in their local directory to where the
playbook is run.
extends_documentation_fragment:
- community.general.cnos
options: {}
'''
EXAMPLES = '''
Tasks : The following are examples of using the module cnos_save. These are
written in the main.yml file of the tasks directory.
---
- name: Test Save
cnos_save:
deviceType: "{{ hostvars[inventory_hostname]['deviceType'] }}"
outputfile: "./results/test_save_{{ inventory_hostname }}_output.txt"
'''
RETURN = '''
msg:
description: Success or failure message
returned: always
type: str
sample: "Switch Running Config is Saved to Startup Config"
'''
import sys
import time
import socket
import array
import json
import time
import re
try:
from ansible_collections.community.general.plugins.module_utils.network.cnos import cnos
HAS_LIB = True
except Exception:
HAS_LIB = False
from ansible.module_utils.basic import AnsibleModule
from collections import defaultdict
def main():
module = AnsibleModule(
argument_spec=dict(
outputfile=dict(required=True),
host=dict(required=False),
username=dict(required=False),
password=dict(required=False, no_log=True),
enablePassword=dict(required=False, no_log=True),
deviceType=dict(required=True),),
supports_check_mode=False)
command = 'write memory'
outputfile = module.params['outputfile']
output = ''
cmd = [{'command': command, 'prompt': None, 'answer': None}]
output = output + str(cnos.run_cnos_commands(module, cmd))
# Save it into the file
file = open(outputfile, "a")
file.write(output)
file.close()
errorMsg = cnos.checkOutputForError(output)
if(errorMsg is None):
module.exit_json(changed=True,
msg="Switch Running Config is Saved to Startup Config ")
else:
module.fail_json(msg=errorMsg)
if __name__ == '__main__':
main()

View file

@ -0,0 +1,114 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
#
# Copyright (C) 2017 Lenovo, Inc.
#
# This file is part of Ansible
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
#
# Module to display running config of Switches
# Lenovo Networking
#
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = '''
---
module: cnos_showrun
author: "Anil Kumar Muraleedharan (@amuraleedhar)"
short_description: Collect the current running configuration on devices running on CNOS
description:
- This module allows you to view the switch running configuration. It
executes the display running-config CLI command on a switch and returns a
file containing the current running configuration of the target network
device. This module uses SSH to manage network device configuration.
The results of the operation will be placed in a directory named 'results'
that must be created by the user in their local directory to where the
playbook is run.
extends_documentation_fragment:
- community.general.cnos
options: {}
'''
EXAMPLES = '''
Tasks : The following are examples of using the module cnos_showrun. These are
written in the main.yml file of the tasks directory.
---
- name: Run show running-config
cnos_showrun:
deviceType: "{{ hostvars[inventory_hostname]['deviceType'] }}"
outputfile: "./results/test_showrun_{{ inventory_hostname }}_output.txt"
'''
RETURN = '''
msg:
description: Success or failure message
returned: always
type: str
sample: "Running Configuration saved in file"
'''
import sys
import time
import socket
import array
import json
import time
import re
try:
from ansible_collections.community.general.plugins.module_utils.network.cnos import cnos
HAS_LIB = True
except Exception:
HAS_LIB = False
from ansible.module_utils.basic import AnsibleModule
from collections import defaultdict
def main():
module = AnsibleModule(
argument_spec=dict(
outputfile=dict(required=True),
host=dict(required=False),
username=dict(required=False),
password=dict(required=False, no_log=True),
enablePassword=dict(required=False, no_log=True),),
supports_check_mode=False)
command = 'show running-config'
outputfile = module.params['outputfile']
output = ''
cmd = [{'command': command, 'prompt': None, 'answer': None}]
output = output + str(cnos.run_cnos_commands(module, cmd))
# Save it into the file
file = open(outputfile, "a")
file.write(output)
file.close()
errorMsg = cnos.checkOutputForError(output)
if(errorMsg is None):
module.exit_json(changed=True,
msg="Running Configuration saved in file ")
else:
module.fail_json(msg=errorMsg)
if __name__ == '__main__':
main()

View file

@ -0,0 +1,288 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
#
# Copyright (C) 2019 Lenovo, Inc.
# (c) 2017, Ansible by Red Hat, inc
# This file is part of Ansible
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
#
# Module to work on Link Aggregation with Lenovo Switches
# Lenovo Networking
#
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = '''
---
module: cnos_static_route
author: "Anil Kumar Muraleedharan (@amuraleedhar)"
short_description: Manage static IP routes on Lenovo CNOS network devices
description:
- This module provides declarative management of static
IP routes on Lenovo CNOS network devices.
notes:
- Tested against CNOS 10.10.1
options:
prefix:
description:
- Network prefix of the static route.
mask:
description:
- Network prefix mask of the static route.
next_hop:
description:
- Next hop IP of the static route.
interface:
description:
- Interface of the static route.
description:
description:
- Name of the static route
aliases: ['description']
admin_distance:
description:
- Admin distance of the static route.
default: 1
tag:
description:
- Set tag of the static route.
aggregate:
description: List of static route definitions.
state:
description:
- State of the static route configuration.
default: present
choices: ['present', 'absent']
'''
EXAMPLES = """
- name: configure static route
cnos_static_route:
prefix: 10.241.107.0
mask: 255.255.255.0
next_hop: 10.241.106.1
- name: configure ultimate route with name and tag
cnos_static_route:
prefix: 10.241.107.0
mask: 255.255.255.0
interface: Ethernet1/13
description: hello world
tag: 100
- name: remove configuration
cnos_static_route:
prefix: 10.241.107.0
mask: 255.255.255.0
next_hop: 10.241.106.0
state: absent
- name: Add static route aggregates
cnos_static_route:
aggregate:
- { prefix: 10.241.107.0, mask: 255.255.255.0, next_hop: 10.241.105.0 }
- { prefix: 10.241.106.0, mask: 255.255.255.0, next_hop: 10.241.104.0 }
- name: Remove static route aggregates
cnos_static_route:
aggregate:
- { prefix: 10.241.107.0, mask: 255.255.255.0, next_hop: 10.241.105.0 }
- { prefix: 10.241.106.0, mask: 255.255.255.0, next_hop: 10.241.104.0 }
state: absent
"""
RETURN = """
commands:
description: The list of configuration mode commands to send to the device
returned: always
type: list
sample:
- ip route 10.241.107.0 255.255.255.0 10.241.106.0
"""
from copy import deepcopy
from re import findall
from ansible_collections.ansible.netcommon.plugins.module_utils.compat import ipaddress
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import validate_ip_address
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import remove_default_spec
from ansible_collections.community.general.plugins.module_utils.network.cnos.cnos import get_config, load_config
from ansible_collections.community.general.plugins.module_utils.network.cnos.cnos import check_args
from ansible_collections.community.general.plugins.module_utils.network.cnos.cnos import cnos_argument_spec
def map_obj_to_commands(want, have):
commands = list()
for w in want:
state = w['state']
command = 'ip route'
prefix = w['prefix']
mask = w['mask']
command = ' '.join((command, prefix, mask))
for key in ['interface', 'next_hop', 'admin_distance', 'tag',
'description']:
if w.get(key):
if key == 'description' and len(w.get(key).split()) > 1:
# name with multiple words needs to be quoted
command = ' '.join((command, key, '"%s"' % w.get(key)))
elif key in ('description', 'tag'):
command = ' '.join((command, key, w.get(key)))
else:
command = ' '.join((command, w.get(key)))
if state == 'absent':
commands.append('no %s' % command)
elif state == 'present':
commands.append(command)
return commands
def map_config_to_obj(module):
obj = []
out = get_config(module, flags='| include ip route')
for line in out.splitlines():
# Split by whitespace but do not split quotes, needed for description
splitted_line = findall(r'[^"\s]\S*|".+?"', line)
route = {}
prefix_with_mask = splitted_line[2]
prefix = None
mask = None
iface = None
nhop = None
if validate_ip_address(prefix_with_mask) is True:
my_net = ipaddress.ip_network(prefix_with_mask)
prefix = str(my_net.network_address)
mask = str(my_net.netmask)
route.update({'prefix': prefix,
'mask': mask, 'admin_distance': '1'})
if splitted_line[3] is not None:
if validate_ip_address(splitted_line[3]) is False:
iface = str(splitted_line[3])
route.update(interface=iface)
if validate_ip_address(splitted_line[4]) is True:
nhop = str(splitted_line[4])
route.update(next_hop=nhop)
if splitted_line[5].isdigit():
route.update(admin_distance=str(splitted_line[5]))
elif splitted_line[4].isdigit():
route.update(admin_distance=str(splitted_line[4]))
else:
if splitted_line[6] is not None and splitted_line[6].isdigit():
route.update(admin_distance=str(splitted_line[6]))
else:
nhop = str(splitted_line[3])
route.update(next_hop=nhop)
if splitted_line[4].isdigit():
route.update(admin_distance=str(splitted_line[4]))
index = 0
for word in splitted_line:
if word in ('tag', 'description'):
route.update(word=splitted_line[index + 1])
index = index + 1
obj.append(route)
return obj
def map_params_to_obj(module, required_together=None):
keys = ['prefix', 'mask', 'state', 'next_hop', 'interface', 'description',
'admin_distance', 'tag']
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)
route = dict((k, v) for k, v in route.items() if v is not None)
module._check_required_together(required_together, route)
obj.append(route)
else:
module._check_required_together(required_together, module.params)
route = dict()
for key in keys:
if module.params.get(key) is not None:
route[key] = module.params.get(key)
obj.append(route)
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'),
interface=dict(type='str'),
description=dict(type='str'),
admin_distance=dict(type='str', default='1'),
tag=dict(type='str'),
state=dict(default='present', choices=['present', 'absent'])
)
aggregate_spec = deepcopy(element_spec)
aggregate_spec['prefix'] = dict(required=True)
# remove default in aggregate spec, to handle common arguments
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 = [['aggregate', 'prefix']]
required_together = [['prefix', 'mask']]
mutually_exclusive = [['aggregate', 'prefix']]
module = AnsibleModule(argument_spec=argument_spec,
required_one_of=required_one_of,
mutually_exclusive=mutually_exclusive,
supports_check_mode=True)
warnings = list()
check_args(module, warnings)
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)
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,387 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
#
# Copyright (C) 2019 Lenovo.
# (c) 2017, Ansible by Red Hat, inc
# This file is part of Ansible
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
#
# Module to work on System Configuration with Lenovo Switches
# Lenovo Networking
#
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = '''
---
module: cnos_system
author: "Anil Kumar Muraleedharan (@amuraleedhar)"
short_description: Manage the system attributes on Lenovo CNOS devices
description:
- This module provides declarative management of node system attributes
on Lenovo CNOS devices. It provides an option to configure host system
parameters or remove those parameters from the device active
configuration.
options:
hostname:
description:
- Configure the device hostname parameter. This option takes an
ASCII string value or keyword 'default'
domain_name:
description:
- Configures the default domain
name suffix to be used when referencing this node by its
FQDN. This argument accepts either a list of domain names or
a list of dicts that configure the domain name and VRF name or
keyword 'default'. See examples.
lookup_enabled:
description:
- Administrative control for enabling or disabling DNS lookups.
When this argument is set to True, lookups are performed and
when it is set to False, lookups are not performed.
type: bool
domain_search:
description:
- Configures a list of domain
name suffixes to search when performing DNS name resolution.
This argument accepts either a list of domain names or
a list of dicts that configure the domain name and VRF name or
keyword 'default'. See examples.
name_servers:
description:
- List of DNS name servers by IP address to use to perform name resolution
lookups. This argument accepts either a list of DNS servers or
a list of hashes that configure the name server and VRF name or
keyword 'default'. See examples.
lookup_source:
description:
- Provides one or more source interfaces to use for performing DNS
lookups. The interface must be a valid interface configured.
on the device.
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
default: present
choices: ['present', 'absent']
'''
EXAMPLES = """
- name: configure hostname and domain-name
cnos_system:
hostname: cnos01
domain_name: test.example.com
- name: remove configuration
cnos_system:
state: absent
- name: configure name servers
cnos_system:
name_servers:
- 8.8.8.8
- 8.8.4.4
- name: configure DNS Lookup sources
cnos_system:
lookup_source: MgmtEth0/0/CPU0/0
lookup_enabled: yes
- name: configure name servers with VRF support
nxos_system:
name_servers:
- { server: 8.8.8.8, vrf: mgmt }
- { server: 8.8.4.4, vrf: mgmt }
"""
RETURN = """
commands:
description: The list of configuration mode commands to send to the device
returned: always
type: list
sample:
- hostname cnos01
- ip domain-name test.example.com vrf default
"""
import re
from ansible_collections.community.general.plugins.module_utils.network.cnos.cnos import get_config, load_config
from ansible_collections.community.general.plugins.module_utils.network.cnos.cnos import cnos_argument_spec
from ansible_collections.community.general.plugins.module_utils.network.cnos.cnos import check_args, debugOutput
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.six import iteritems
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 ComplexList
_CONFIGURED_VRFS = None
def map_obj_to_commands(want, have, module):
commands = list()
state = module.params['state']
def needs_update(x):
return want.get(x) and (want.get(x) != have.get(x))
def difference(x, y, z):
return [item for item in x[z] if item not in y[z]]
if state == 'absent':
if have['hostname']:
commands.append('no hostname')
for item in have['domain_name']:
my_vrf = 'default'
if item['vrf'] is not None:
my_vrf = item['vrf']
cmd = 'no ip domain-name {0} vrf {1}'.format(item['name'], my_vrf)
commands.append(cmd)
for item in have['domain_search']:
my_vrf = 'default'
if item['vrf'] is not None:
my_vrf = item['vrf']
cmd = 'no ip domain-list {0} vrf {1}'.format(item['name'], my_vrf)
commands.append(cmd)
for item in have['name_servers']:
my_vrf = 'default'
if item['vrf'] is not None:
my_vrf = item['vrf']
cmd = 'no ip name-server {0} vrf {1}'.format(item['server'], my_vrf)
commands.append(cmd)
if state == 'present':
if needs_update('hostname'):
if want['hostname'] == 'default':
if have['hostname']:
commands.append('no hostname')
else:
commands.append('hostname %s' % want['hostname'])
if want.get('lookup_enabled') is not None:
if have.get('lookup_enabled') != want.get('lookup_enabled'):
cmd = 'ip domain-lookup'
if want['lookup_enabled'] is False:
cmd = 'no %s' % cmd
commands.append(cmd)
if want['domain_name']:
if want.get('domain_name')[0]['name'] == 'default':
if have['domain_name']:
for item in have['domain_name']:
my_vrf = 'default'
if item['vrf'] is not None:
my_vrf = item['vrf']
cmd = 'no ip domain-name {0} vrf {1}'.format(item['name'], my_vrf)
commands.append(cmd)
else:
for item in difference(have, want, 'domain_name'):
my_vrf = 'default'
if item['vrf'] is not None:
my_vrf = item['vrf']
cmd = 'no ip domain-name {0} vrf {1}'.format(item['name'], my_vrf)
commands.append(cmd)
for item in difference(want, have, 'domain_name'):
my_vrf = 'default'
if item['vrf'] is not None:
my_vrf = item['vrf']
cmd = 'ip domain-name {0} vrf {1}'.format(item['name'], my_vrf)
commands.append(cmd)
if want['domain_search']:
if want.get('domain_search')[0]['name'] == 'default':
if have['domain_search']:
for item in have['domain_search']:
my_vrf = 'default'
if item['vrf'] is not None:
my_vrf = item['vrf']
cmd = 'no ip domain-list {0} vrf {1}'.format(item['name'], my_vrf)
commands.append(cmd)
else:
for item in difference(have, want, 'domain_search'):
my_vrf = 'default'
if item['vrf'] is not None:
my_vrf = item['vrf']
cmd = 'no ip domain-list {0} vrf {1}'.format(item['name'], my_vrf)
commands.append(cmd)
for item in difference(want, have, 'domain_search'):
my_vrf = 'default'
if item['vrf'] is not None:
my_vrf = item['vrf']
cmd = 'ip domain-list {0} vrf {1}'.format(item['name'], my_vrf)
commands.append(cmd)
if want['name_servers']:
if want.get('name_servers')[0]['server'] == 'default':
if have['name_servers']:
for item in have['name_servers']:
my_vrf = 'default'
if item['vrf'] is not None:
my_vrf = item['vrf']
cmd = 'no ip name-server {0} vrf {1}'.format(item['server'], my_vrf)
commands.append(cmd)
else:
for item in difference(have, want, 'name_servers'):
my_vrf = 'default'
if item['vrf'] is not None:
my_vrf = item['vrf']
cmd = 'no ip name-server {0} vrf {1}'.format(item['server'], my_vrf)
commands.append(cmd)
for item in difference(want, have, 'name_servers'):
my_vrf = 'default'
if item['vrf'] is not None:
my_vrf = item['vrf']
cmd = 'ip name-server {0} vrf {1}'.format(item['server'], my_vrf)
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_name(config):
objects = list()
myconf = config.splitlines()
for line in myconf:
if 'ip domain-name' in line:
datas = line.split()
objects.append({'name': datas[2], 'vrf': datas[4]})
return objects
def parse_domain_search(config):
objects = list()
myconf = config.splitlines()
for line in myconf:
if 'ip domain-list' in line:
datas = line.split()
objects.append({'name': datas[2], 'vrf': datas[4]})
return objects
def parse_name_servers(config):
objects = list()
myconf = config.splitlines()
for line in myconf:
if 'ip name-server' in line:
datas = line.split()
objects.append({'server': datas[2], 'vrf': datas[4]})
return objects
def map_config_to_obj(module):
config = get_config(module)
configobj = NetworkConfig(indent=2, contents=config)
return {
'hostname': parse_hostname(config),
'lookup_enabled': 'no ip domain-lookup' not in config,
'domain_name': parse_domain_name(config),
'domain_search': parse_domain_search(config),
'name_servers': parse_name_servers(config),
}
def map_params_to_obj(module):
obj = {
'hostname': module.params['hostname'],
'lookup_enabled': module.params['lookup_enabled'],
}
domain_name = ComplexList(dict(
name=dict(key=True),
vrf=dict()
), module)
domain_search = ComplexList(dict(
name=dict(key=True),
vrf=dict()
), module)
name_servers = ComplexList(dict(
server=dict(key=True),
vrf=dict()
), module)
for arg, cast in [('domain_name', domain_name),
('domain_search', domain_search),
('name_servers', name_servers)]:
if module.params[arg] is not None:
obj[arg] = cast(module.params[arg])
else:
obj[arg] = None
return obj
def main():
""" main entry point for module execution
"""
argument_spec = dict(
hostname=dict(),
lookup_enabled=dict(type='bool'),
# { name: <str>, vrf: <str> }
domain_name=dict(type='list'),
# {name: <str>, vrf: <str> }
domain_search=dict(type='list'),
# { server: <str>; vrf: <str> }
name_servers=dict(type='list'),
lookup_source=dict(type='str'),
state=dict(default='present', choices=['present', 'absent'])
)
module = AnsibleModule(argument_spec=argument_spec,
supports_check_mode=True)
warnings = list()
check_args(module, warnings)
result = {'changed': False}
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,151 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
#
# Copyright (C) 2017 Lenovo, Inc.
#
# This file is part of Ansible
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
#
# Module to send CLI templates to Lenovo Switches
# Lenovo Networking
#
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = '''
---
module: cnos_template
author: "Anil Kumar Muraleedharan (@amuraleedhar)"
short_description: Manage switch configuration using templates on devices running Lenovo CNOS
description:
- This module allows you to work with the running configuration of a switch. It provides a way
to execute a set of CNOS commands on a switch by evaluating the current running configuration
and executing the commands only if the specific settings have not been already configured.
The configuration source can be a set of commands or a template written in the Jinja2 templating language.
This module uses SSH to manage network device configuration.
The results of the operation will be placed in a directory named 'results'
that must be created by the user in their local directory to where the playbook is run.
extends_documentation_fragment:
- community.general.cnos
options:
commandfile:
description:
- This specifies the path to the CNOS command file which needs to be applied. This usually
comes from the commands folder. Generally this file is the output of the variables applied
on a template file. So this command is preceded by a template module.
Note The command file must contain the Ansible keyword {{ inventory_hostname }} in its
filename to ensure that the command file is unique for each switch and condition.
If this is omitted, the command file will be overwritten during iteration. For example,
commandfile=./commands/clos_leaf_bgp_{{ inventory_hostname }}_commands.txt
required: true
default: Null
'''
EXAMPLES = '''
Tasks : The following are examples of using the module cnos_template. These are written in the main.yml file of the tasks directory.
---
- name: Replace Config CLI command template with values
template:
src: demo_template.j2
dest: "./commands/demo_template_{{ inventory_hostname }}_commands.txt"
vlanid1: 13
slot_chassis_number1: "1/2"
portchannel_interface_number1: 100
portchannel_mode1: "active"
- name: Applying CLI commands on Switches
cnos_template:
deviceType: "{{ hostvars[inventory_hostname]['deviceType'] }}"
commandfile: "./commands/demo_template_{{ inventory_hostname }}_commands.txt"
outputfile: "./results/demo_template_command_{{ inventory_hostname }}_output.txt"
'''
RETURN = '''
msg:
description: Success or failure message
returned: always
type: str
sample: "Template Applied."
'''
import sys
import time
import socket
import array
import json
import time
import re
import os
try:
from ansible_collections.community.general.plugins.module_utils.network.cnos import cnos
HAS_LIB = True
except Exception:
HAS_LIB = False
from ansible.module_utils.basic import AnsibleModule
from collections import defaultdict
def main():
module = AnsibleModule(
argument_spec=dict(
commandfile=dict(required=True),
outputfile=dict(required=True),
host=dict(required=False),
deviceType=dict(required=True),
username=dict(required=False),
password=dict(required=False, no_log=True),
enablePassword=dict(required=False, no_log=True),),
supports_check_mode=False)
commandfile = module.params['commandfile']
outputfile = module.params['outputfile']
output = ''
# Send commands one by one to the device
f = open(commandfile, "r")
cmd = []
for line in f:
# Omit the comment lines in template file
if not line.startswith("#"):
command = line.strip()
inner_cmd = [{'command': command, 'prompt': None, 'answer': None}]
cmd.extend(inner_cmd)
# Write to memory
save_cmd = [{'command': 'save', 'prompt': None, 'answer': None}]
cmd.extend(save_cmd)
output = output + str(cnos.run_cnos_commands(module, cmd))
# Write output to file
path = outputfile.rsplit('/', 1)
# cnos.debugOutput(path[0])
if not os.path.exists(path[0]):
os.makedirs(path[0])
file = open(outputfile, "a")
file.write(output)
file.close()
# Logic to check when changes occur or not
errorMsg = cnos.checkOutputForError(output)
if(errorMsg is None):
module.exit_json(changed=True, msg="Template Applied")
else:
module.fail_json(msg=errorMsg)
if __name__ == '__main__':
main()

View file

@ -0,0 +1,390 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
#
# Copyright (C) 2019 Lenovo.
# (c) 2017, Ansible by Red Hat, inc
# This file is part of Ansible
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
#
# Module to work on management of local users on Lenovo CNOS Switches
# Lenovo Networking
#
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = '''
---
module: cnos_user
author: "Anil Kumar Muraleedharan (@amuraleedhar)"
short_description: Manage the collection of local users on Lenovo CNOS devices
description:
- This module provides declarative management of the local usernames
configured on Lenovo CNOS devices. It allows playbooks to manage
either individual usernames or the collection of usernames in the
current running config. It also supports purging usernames from the
configuration that are not explicitly defined.
options:
aggregate:
description:
- The set of username objects to be configured on the remote
Lenovo CNOS 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']
name:
description:
- The username to be configured on the remote Lenovo CNOS
device. This argument accepts a string value and is mutually
exclusive with the C(aggregate) argument.
configured_password:
description:
- The password to be configured on the network device. The
password needs to be provided in cleartext and it will be encrypted
on the device.
Please note that this option is not same as C(provider password).
update_password:
description:
- Since passwords are encrypted in the device running config, 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']
role:
description:
- The C(role) argument configures the role for the username in the
device running configuration. The argument accepts a string value
defining the role name. This argument does not check if the role
has been configured on the device.
aliases: ['roles']
sshkey:
description:
- The C(sshkey) argument defines the SSH public key to configure
for the username. This argument accepts a valid SSH key value.
purge:
description:
- The C(purge) argument instructs the module to consider the
resource definition absolute. It will remove any previously
configured usernames on the device with the exception of the
`admin` user which cannot be deleted per cnos constraints.
type: bool
default: 'no'
state:
description:
- The C(state) argument 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']
'''
EXAMPLES = """
- name: create a new user
cnos_user:
name: ansible
sshkey: "{{ lookup('file', '~/.ssh/id_rsa.pub') }}"
state: present
- name: remove all users except admin
cnos_user:
purge: yes
- name: set multiple users role
aggregate:
- name: netop
- name: netend
role: network-operator
state: present
"""
RETURN = """
commands:
description: The list of configuration mode commands to send to the device
returned: always
type: list
sample:
- name ansible
- name ansible password password
start:
description: The time the job started
returned: always
type: str
sample: "2016-11-16 10:38:15.126146"
end:
description: The time the job ended
returned: always
type: str
sample: "2016-11-16 10:38:25.595612"
delta:
description: The time elapsed to perform all operations
returned: always
type: str
sample: "0:00:10.469466"
"""
import re
from copy import deepcopy
from functools import partial
from ansible_collections.community.general.plugins.module_utils.network.cnos.cnos import run_commands, load_config
from ansible_collections.community.general.plugins.module_utils.network.cnos.cnos import get_config
from ansible_collections.community.general.plugins.module_utils.network.cnos.cnos import cnos_argument_spec
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.six import string_types, iteritems
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import to_list
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import remove_default_spec
from ansible_collections.community.general.plugins.module_utils.network.cnos.cnos import get_user_roles
def validate_roles(value, module):
for item in value:
if item not in get_user_roles():
module.fail_json(msg='invalid role specified')
def map_obj_to_commands(updates, module):
commands = list()
state = module.params['state']
update_password = module.params['update_password']
for update in updates:
want, have = update
def needs_update(x):
return want.get(x) and (want.get(x) != have.get(x))
def add(x):
return commands.append('username %s %s' % (want['name'], x))
def remove(x):
return commands.append('no username %s %s' % (want['name'], x))
if want['state'] == 'absent':
commands.append('no username %s' % want['name'])
continue
if want['state'] == 'present' and not have:
commands.append('username %s' % want['name'])
if needs_update('configured_password'):
if update_password == 'always' or not have:
add('password %s' % want['configured_password'])
if needs_update('sshkey'):
add('sshkey %s' % want['sshkey'])
if want['roles']:
if have:
for item in set(have['roles']).difference(want['roles']):
remove('role %s' % item)
for item in set(want['roles']).difference(have['roles']):
add('role %s' % item)
else:
for item in want['roles']:
add('role %s' % item)
return commands
def parse_password(data):
if 'no password set' in data:
return None
return '<PASSWORD>'
def parse_roles(data):
roles = list()
if 'role:' in data:
items = data.split()
my_item = items[items.index('role:') + 1]
roles.append(my_item)
return roles
def parse_username(data):
name = data.split(' ', 1)[0]
username = name[1:]
return username
def parse_sshkey(data):
key = None
if 'sskkey:' in data:
items = data.split()
key = items[items.index('sshkey:') + 1]
return key
def map_config_to_obj(module):
out = run_commands(module, ['show user-account'])
data = out[0]
objects = list()
datum = data.split('User')
for item in datum:
objects.append({
'name': parse_username(item),
'configured_password': parse_password(item),
'sshkey': parse_sshkey(item),
'roles': parse_roles(item),
'state': 'present'
})
return objects
def get_param_value(key, item, module):
# if key doesn't exist in the item, get it from module.params
if not item.get(key):
value = module.params[key]
# if key does exist, do a type check on it to validate it
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]
return value
def map_params_to_obj(module):
aggregate = module.params['aggregate']
if not aggregate:
if not module.params['name'] and module.params['purge']:
return list()
elif not module.params['name']:
module.fail_json(msg='username is required')
else:
collection = [{'name': module.params['name']}]
else:
collection = list()
for item in aggregate:
if not isinstance(item, dict):
collection.append({'name': item})
elif 'name' not in item:
module.fail_json(msg='name is required')
else:
collection.append(item)
objects = list()
for item in collection:
get_value = partial(get_param_value, item=item, module=module)
item.update({
'configured_password': get_value('configured_password'),
'sshkey': get_value('sshkey'),
'roles': get_value('roles'),
'state': get_value('state')
})
for key, value in iteritems(item):
if value:
# validate the param value (if validator func exists)
validator = globals().get('validate_%s' % key)
if all((value, validator)):
validator(value, module)
objects.append(item)
return objects
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 item:
for key, value in iteritems(entry):
if value and value != item[key]:
updates.append((entry, item))
return updates
def main():
""" main entry point for module execution
"""
element_spec = dict(
name=dict(),
configured_password=dict(no_log=True),
update_password=dict(default='always', choices=['on_create', 'always']),
roles=dict(type='list', aliases=['role']),
sshkey=dict(),
state=dict(default='present', choices=['present', 'absent'])
)
aggregate_spec = deepcopy(element_spec)
# remove default in aggregate spec, to handle common arguments
remove_default_spec(aggregate_spec)
argument_spec = dict(
aggregate=dict(type='list', elements='dict',
options=aggregate_spec, aliases=['collection', 'users']),
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)
warnings = list()
result = {'changed': False}
result['warnings'] = warnings
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':
if not item.strip():
continue
item = item.replace("\\", "\\\\")
commands.append('no username %s' % item)
result['commands'] = commands
# the cnos cli prevents this by rule but still
if 'no username admin' in commands:
module.fail_json(msg='Cannot delete the `admin` account')
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,446 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
#
# Copyright (C) 2017 Lenovo, Inc.
#
# This file is part of Ansible
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
#
# Module to send VLAG commands to Lenovo Switches
# Lenovo Networking
#
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = '''
---
module: cnos_vlag
author: "Anil Kumar Muraleedharan (@amuraleedhar)"
short_description: Manage VLAG resources and attributes on devices running
Lenovo CNOS
description:
- This module allows you to work with virtual Link Aggregation Groups
(vLAG) related configurations. The operators used are overloaded to ensure
control over switch vLAG configurations. Apart from the regular device
connection related attributes, there are four vLAG arguments which are
overloaded variables that will perform further configurations. They are
vlagArg1, vlagArg2, vlagArg3, and vlagArg4. For more details on how to use
these arguments, see [Overloaded Variables].
This module uses SSH to manage network device configuration.
The results of the operation will be placed in a directory named 'results'
that must be created by the user in their local directory to where the
playbook is run.
extends_documentation_fragment:
- community.general.cnos
options:
vlagArg1:
description:
- This is an overloaded vlag first argument. Usage of this argument can
be found is the User Guide referenced above.
required: Yes
default: Null
choices: [enable, auto-recovery,config-consistency,isl,mac-address-table,
peer-gateway,priority,startup-delay,tier-id,vrrp,instance,hlthchk]
vlagArg2:
description:
- This is an overloaded vlag second argument. Usage of this argument can
be found is the User Guide referenced above.
required: No
default: Null
choices: [Interval in seconds,disable or strict,Port Aggregation Number,
VLAG priority,Delay time in seconds,VLAG tier-id value,
VLAG instance number,keepalive-attempts,keepalive-interval,
retry-interval,peer-ip]
vlagArg3:
description:
- This is an overloaded vlag third argument. Usage of this argument can
be found is the User Guide referenced above.
required: No
default: Null
choices: [enable or port-aggregation,Number of keepalive attempts,
Interval in seconds,Interval in seconds,
VLAG health check peer IP4 address]
vlagArg4:
description:
- This is an overloaded vlag fourth argument. Usage of this argument can
be found is the User Guide referenced above.
required: No
default: Null
choices: [Port Aggregation Number,default or management]
'''
EXAMPLES = '''
Tasks : The following are examples of using the module cnos_vlag. These are
written in the main.yml file of the tasks directory.
---
- name: Test Vlag - enable
cnos_vlag:
deviceType: "{{ hostvars[inventory_hostname]['deviceType']}}"
outputfile: "./results/cnos_vlag_{{ inventory_hostname }}_output.txt"
vlagArg1: "enable"
- name: Test Vlag - autorecovery
cnos_vlag:
deviceType: "{{ hostvars[inventory_hostname]['deviceType']}}"
outputfile: "./results/cnos_vlag_{{ inventory_hostname }}_output.txt"
vlagArg1: "auto-recovery"
vlagArg2: 266
- name: Test Vlag - config-consistency
cnos_vlag:
deviceType: "{{ hostvars[inventory_hostname]['deviceType']}}"
outputfile: "./results/cnos_vlag_{{ inventory_hostname }}_output.txt"
vlagArg1: "config-consistency"
vlagArg2: "strict"
- name: Test Vlag - isl
cnos_vlag:
deviceType: "{{ hostvars[inventory_hostname]['deviceType']}}"
outputfile: "./results/cnos_vlag_{{ inventory_hostname }}_output.txt"
vlagArg1: "isl"
vlagArg2: 23
- name: Test Vlag - mac-address-table
cnos_vlag:
deviceType: "{{ hostvars[inventory_hostname]['deviceType']}}"
outputfile: "./results/cnos_vlag_{{ inventory_hostname }}_output.txt"
vlagArg1: "mac-address-table"
- name: Test Vlag - peer-gateway
cnos_vlag:
deviceType: "{{ hostvars[inventory_hostname]['deviceType']}}"
outputfile: "./results/cnos_vlag_{{ inventory_hostname }}_output.txt"
vlagArg1: "peer-gateway"
- name: Test Vlag - priority
cnos_vlag:
deviceType: "{{ hostvars[inventory_hostname]['deviceType']}}"
outputfile: "./results/cnos_vlag_{{ inventory_hostname }}_output.txt"
vlagArg1: "priority"
vlagArg2: 1313
- name: Test Vlag - startup-delay
cnos_vlag:
deviceType: "{{ hostvars[inventory_hostname]['deviceType']}}"
outputfile: "./results/cnos_vlag_{{ inventory_hostname }}_output.txt"
vlagArg1: "startup-delay"
vlagArg2: 323
- name: Test Vlag - tier-id
cnos_vlag:
deviceType: "{{ hostvars[inventory_hostname]['deviceType']}}"
outputfile: "./results/cnos_vlag_{{ inventory_hostname }}_output.txt"
vlagArg1: "tier-id"
vlagArg2: 313
- name: Test Vlag - vrrp
cnos_vlag:
deviceType: "{{ hostvars[inventory_hostname]['deviceType']}}"
outputfile: "./results/cnos_vlag_{{ inventory_hostname }}_output.txt"
vlagArg1: "vrrp"
- name: Test Vlag - instance
cnos_vlag:
deviceType: "{{ hostvars[inventory_hostname]['deviceType']}}"
outputfile: "./results/cnos_vlag_{{ inventory_hostname }}_output.txt"
vlagArg1: "instance"
vlagArg2: 33
vlagArg3: 333
- name: Test Vlag - instance2
cnos_vlag:
deviceType: "{{ hostvars[inventory_hostname]['deviceType']}}"
outputfile: "./results/cnos_vlag_{{ inventory_hostname }}_output.txt"
vlagArg1: "instance"
vlagArg2: "33"
- name: Test Vlag - keepalive-attempts
cnos_vlag:
deviceType: "{{ hostvars[inventory_hostname]['deviceType']}}"
outputfile: "./results/cnos_vlag_{{ inventory_hostname }}_output.txt"
vlagArg1: "hlthchk"
vlagArg2: "keepalive-attempts"
vlagArg3: 13
- name: Test Vlag - keepalive-interval
cnos_vlag:
deviceType: "{{ hostvars[inventory_hostname]['deviceType']}}"
outputfile: "./results/cnos_vlag_{{ inventory_hostname }}_output.txt"
vlagArg1: "hlthchk"
vlagArg2: "keepalive-interval"
vlagArg3: 131
- name: Test Vlag - retry-interval
cnos_vlag:
deviceType: "{{ hostvars[inventory_hostname]['deviceType']}}"
outputfile: "./results/cnos_vlag_{{ inventory_hostname }}_output.txt"
vlagArg1: "hlthchk"
vlagArg2: "retry-interval"
vlagArg3: 133
- name: Test Vlag - peer ip
cnos_vlag:
deviceType: "{{ hostvars[inventory_hostname]['deviceType']}}"
outputfile: "./results/cnos_vlag_{{ inventory_hostname }}_output.txt"
vlagArg1: "hlthchk"
vlagArg2: "peer-ip"
vlagArg3: "1.2.3.4"
'''
RETURN = '''
msg:
description: Success or failure message
returned: always
type: str
sample: "vLAG configurations accomplished"
'''
import sys
import time
import socket
import array
import json
import time
import re
try:
from ansible_collections.community.general.plugins.module_utils.network.cnos import cnos
HAS_LIB = True
except Exception:
HAS_LIB = False
from ansible.module_utils.basic import AnsibleModule
from collections import defaultdict
def vlagConfig(module, prompt, answer):
retVal = ''
# vlag config command happens here.
command = 'vlag '
vlagArg1 = module.params['vlagArg1']
vlagArg2 = module.params['vlagArg2']
vlagArg3 = module.params['vlagArg3']
vlagArg4 = module.params['vlagArg4']
deviceType = module.params['deviceType']
if(vlagArg1 == "enable"):
# debugOutput("enable")
command = command + vlagArg1 + " "
elif(vlagArg1 == "auto-recovery"):
# debugOutput("auto-recovery")
command = command + vlagArg1 + " "
value = cnos.checkSanityofVariable(
deviceType, "vlag_auto_recovery", vlagArg2)
if(value == "ok"):
command = command + vlagArg2
else:
retVal = "Error-160"
return retVal
elif(vlagArg1 == "config-consistency"):
# debugOutput("config-consistency")
command = command + vlagArg1 + " "
value = cnos.checkSanityofVariable(
deviceType, "vlag_config_consistency", vlagArg2)
if(value == "ok"):
command = command + vlagArg2
else:
retVal = "Error-161"
return retVal
elif(vlagArg1 == "isl"):
# debugOutput("isl")
command = command + vlagArg1 + " port-channel "
value = cnos.checkSanityofVariable(
deviceType, "vlag_port_aggregation", vlagArg2)
if(value == "ok"):
command = command + vlagArg2
else:
retVal = "Error-162"
return retVal
elif(vlagArg1 == "mac-address-table"):
# debugOutput("mac-address-table")
command = command + vlagArg1 + " refresh"
elif(vlagArg1 == "peer-gateway"):
# debugOutput("peer-gateway")
command = command + vlagArg1 + " "
elif(vlagArg1 == "priority"):
# debugOutput("priority")
command = command + vlagArg1 + " "
value = cnos.checkSanityofVariable(deviceType, "vlag_priority",
vlagArg2)
if(value == "ok"):
command = command + vlagArg2
else:
retVal = "Error-163"
return retVal
elif(vlagArg1 == "startup-delay"):
# debugOutput("startup-delay")
command = command + vlagArg1 + " "
value = cnos.checkSanityofVariable(
deviceType, "vlag_startup_delay", vlagArg2)
if(value == "ok"):
command = command + vlagArg2
else:
retVal = "Error-164"
return retVal
elif(vlagArg1 == "tier-id"):
# debugOutput("tier-id")
command = command + vlagArg1 + " "
value = cnos.checkSanityofVariable(deviceType, "vlag_tier_id", vlagArg2)
if(value == "ok"):
command = command + vlagArg2
else:
retVal = "Error-165"
return retVal
elif(vlagArg1 == "vrrp"):
# debugOutput("vrrp")
command = command + vlagArg1 + " active"
elif(vlagArg1 == "instance"):
# debugOutput("instance")
command = command + vlagArg1 + " "
value = cnos.checkSanityofVariable(deviceType, "vlag_instance",
vlagArg2)
if(value == "ok"):
command = command + vlagArg2
if(vlagArg3 is not None):
command = command + " port-channel "
value = cnos.checkSanityofVariable(
deviceType, "vlag_port_aggregation", vlagArg3)
if(value == "ok"):
command = command + vlagArg3
else:
retVal = "Error-162"
return retVal
else:
command = command + " enable "
else:
retVal = "Error-166"
return retVal
elif(vlagArg1 == "hlthchk"):
# debugOutput("hlthchk")
command = command + vlagArg1 + " "
value = cnos.checkSanityofVariable(
deviceType, "vlag_hlthchk_options", vlagArg2)
if(value == "ok"):
if(vlagArg2 == "keepalive-attempts"):
value = cnos.checkSanityofVariable(
deviceType, "vlag_keepalive_attempts", vlagArg3)
if(value == "ok"):
command = command + vlagArg2 + " " + vlagArg3
else:
retVal = "Error-167"
return retVal
elif(vlagArg2 == "keepalive-interval"):
value = cnos.checkSanityofVariable(
deviceType, "vlag_keepalive_interval", vlagArg3)
if(value == "ok"):
command = command + vlagArg2 + " " + vlagArg3
else:
retVal = "Error-168"
return retVal
elif(vlagArg2 == "retry-interval"):
value = cnos.checkSanityofVariable(
deviceType, "vlag_retry_interval", vlagArg3)
if(value == "ok"):
command = command + vlagArg2 + " " + vlagArg3
else:
retVal = "Error-169"
return retVal
elif(vlagArg2 == "peer-ip"):
# Here I am not taking care of IPV6 option.
value = cnos.checkSanityofVariable(
deviceType, "vlag_peerip", vlagArg3)
if(value == "ok"):
command = command + vlagArg2 + " " + vlagArg3
if(vlagArg4 is not None):
value = cnos.checkSanityofVariable(
deviceType, "vlag_peerip_vrf", vlagArg4)
if(value == "ok"):
command = command + " vrf " + vlagArg4
else:
retVal = "Error-170"
return retVal
else:
retVal = "Error-171"
return retVal
else:
retVal = "Error-172"
return retVal
# debugOutput(command)
cmd = [{'command': command, 'prompt': None, 'answer': None}]
retVal = retVal + str(cnos.run_cnos_commands(module, cmd))
return retVal
# EOM
def main():
#
# Define parameters for vlag creation entry
#
module = AnsibleModule(
argument_spec=dict(
outputfile=dict(required=True),
host=dict(required=False),
username=dict(required=False),
password=dict(required=False, no_log=True),
enablePassword=dict(required=False, no_log=True),
deviceType=dict(required=True),
vlagArg1=dict(required=True),
vlagArg2=dict(required=False),
vlagArg3=dict(required=False),
vlagArg4=dict(required=False),),
supports_check_mode=False)
outputfile = module.params['outputfile']
output = ""
# Send the CLi command
output = output + str(vlagConfig(module, '(config)#', None))
# Save it into the file
file = open(outputfile, "a")
file.write(output)
file.close()
# need to add logic to check when changes occur or not
errorMsg = cnos.checkOutputForError(output)
if(errorMsg is None):
module.exit_json(changed=True, msg="VLAG configurations accomplished")
else:
module.fail_json(msg=errorMsg)
if __name__ == '__main__':
main()

View file

@ -0,0 +1,409 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright (C) 2017 Lenovo, Inc.
# (c) 2017, Ansible by Red Hat, inc
# This file is part of Ansible
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
#
# Module to send VLAN commands to Lenovo Switches
# Overloading aspect of vlan creation in a range is pending
# Lenovo Networking
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = '''
---
module: cnos_vlan
author: "Anil Kumar Mureleedharan(@amuraleedhar)"
short_description: Manage VLANs on CNOS network devices
description:
- This module provides declarative management of VLANs
on Lenovo CNOS network devices.
notes:
- Tested against CNOS 10.8.1
options:
name:
description:
- Name of the VLAN.
vlan_id:
description:
- ID of the VLAN. Range 1-4094.
required: true
interfaces:
description:
- List of interfaces that should be associated to the VLAN.
required: true
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.
delay:
description:
- Delay the play should wait to check for declarative intent params
values.
default: 10
aggregate:
description: List of VLANs definitions.
purge:
description:
- Purge VLANs not defined in the I(aggregate) parameter.
default: no
type: bool
state:
description:
- State of the VLAN configuration.
default: present
choices: ['present', 'absent', 'active', 'suspend']
provider:
description:
- B(Deprecated)
- "Starting with Ansible 2.5 we recommend using C(connection: network_cli)."
- For more information please see the L(CNOS Platform Options guide, ../network/user_guide/platform_cnos.html).
- HORIZONTALLINE
- A dict object containing connection details.
suboptions:
host:
description:
- Specifies the DNS host name or address for connecting to the remote
device over the specified transport. The value of host is used as
the destination address for the transport.
required: true
port:
description:
- Specifies the port to use when building the connection to the remote device.
default: 22
username:
description:
- Configures the username to use to authenticate the connection to
the remote device. This value is used to authenticate
the SSH session. If the value is not specified in the task, the
value of environment variable C(ANSIBLE_NET_USERNAME) will be used instead.
password:
description:
- Specifies the password to use to authenticate the connection to
the remote device. This value is used to authenticate
the SSH session. If the value is not specified in the task, the
value of environment variable C(ANSIBLE_NET_PASSWORD) will be used instead.
timeout:
description:
- Specifies the timeout in seconds for communicating with the network device
for either connecting or sending commands. If the timeout is
exceeded before the operation is completed, the module will error.
default: 10
ssh_keyfile:
description:
- Specifies the SSH key to use to authenticate the connection to
the remote device. This value is the path to the
key used to authenticate the SSH session. If the value is not specified
in the task, the value of environment variable C(ANSIBLE_NET_SSH_KEYFILE)
will be used instead.
authorize:
description:
- Instructs the module to enter privileged mode on the remote device
before sending any commands. If not specified, the device will
attempt to execute all commands in non-privileged mode. If the value
is not specified in the task, the value of environment variable
C(ANSIBLE_NET_AUTHORIZE) will be used instead.
type: bool
default: 'no'
auth_pass:
description:
- Specifies the password to use if required to enter privileged mode
on the remote device. If I(authorize) is false, then this argument
does nothing. If the value is not specified in the task, the value of
environment variable C(ANSIBLE_NET_AUTH_PASS) will be used instead.
'''
EXAMPLES = """
- name: Create vlan
cnos_vlan:
vlan_id: 100
name: test-vlan
state: present
- name: Add interfaces to VLAN
cnos_vlan:
vlan_id: 100
interfaces:
- Ethernet1/33
- Ethernet1/44
- name: Check if interfaces is assigned to VLAN
cnos_vlan:
vlan_id: 100
associated_interfaces:
- Ethernet1/33
- Ethernet1/44
- name: Delete vlan
cnos_vlan:
vlan_id: 100
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
import time
from copy import deepcopy
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import remove_default_spec
from ansible_collections.community.general.plugins.module_utils.network.cnos.cnos import load_config, run_commands
from ansible_collections.community.general.plugins.module_utils.network.cnos.cnos import debugOutput, check_args
from ansible_collections.community.general.plugins.module_utils.network.cnos.cnos import cnos_argument_spec
from ansible.module_utils._text import to_text
def search_obj_in_list(vlan_id, lst):
obj = list()
for o in lst:
if o['vlan_id'] == vlan_id:
return o
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']
name = w['name']
interfaces = w['interfaces']
state = w['state']
obj_in_have = search_obj_in_list(vlan_id, have)
if state == 'absent':
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('name {0}'.format(name))
if interfaces:
for i in interfaces:
commands.append('interface {0}'.format(i))
commands.append('switchport mode access')
commands.append('switchport access vlan {0}'.format(vlan_id))
else:
if name:
if name != obj_in_have['name']:
commands.append('vlan {0}'.format(vlan_id))
commands.append('name {0}'.format(name))
if interfaces:
if not obj_in_have['interfaces']:
for i in interfaces:
commands.append('vlan {0}'.format(vlan_id))
commands.append('interface {0}'.format(i))
commands.append('switchport mode access')
commands.append('switchport access vlan {0}'.format(vlan_id))
elif set(interfaces) != set(obj_in_have['interfaces']):
missing_interfaces = list(set(interfaces) - set(obj_in_have['interfaces']))
for i in missing_interfaces:
commands.append('vlan {0}'.format(vlan_id))
commands.append('interface {0}'.format(i))
commands.append('switchport mode access')
commands.append('switchport access vlan {0}'.format(vlan_id))
superfluous_interfaces = list(set(obj_in_have['interfaces']) - set(interfaces))
for i in superfluous_interfaces:
commands.append('vlan {0}'.format(vlan_id))
commands.append('interface {0}'.format(i))
commands.append('switchport mode access')
commands.append('no switchport access vlan')
else:
commands.append('vlan {0}'.format(vlan_id))
if name:
commands.append('name {0}'.format(name))
commands.append('state {0}'.format(state))
if purge:
for h in have:
obj_in_want = search_obj_in_list(h['vlan_id'], want)
if not obj_in_want and h['vlan_id'] != '1':
commands.append('no vlan {0}'.format(h['vlan_id']))
return commands
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['vlan_id'] = str(d['vlan_id'])
obj.append(d)
else:
obj.append({
'vlan_id': str(module.params['vlan_id']),
'name': module.params['name'],
'interfaces': module.params['interfaces'],
# 'associated_interfaces': module.params['associated_interfaces'],
'state': module.params['state']
})
return obj
def parse_to_logical_rows(out):
relevant_data = False
cur_row = []
for line in out.splitlines():
if not line:
"""Skip empty lines."""
continue
if '0' < line[0] <= '9':
"""Line starting with a number."""
if len(cur_row) > 0:
yield cur_row
cur_row = [] # Reset it to hold a next chunk
relevant_data = True
if relevant_data:
data = line.strip().split('(')
cur_row.append(data[0])
yield cur_row
def parse_to_obj(logical_rows):
first_row = logical_rows[0]
rest_rows = logical_rows[1:]
vlan_data = first_row.split()
obj = {}
obj['vlan_id'] = vlan_data[0]
obj['name'] = vlan_data[1]
obj['state'] = vlan_data[2]
obj['interfaces'] = rest_rows
return obj
def parse_vlan_brief(vlan_out):
return [parse_to_obj(r) for r in parse_to_logical_rows(vlan_out)]
def map_config_to_obj(module):
return parse_vlan_brief(run_commands(module, ['show vlan brief'])[0])
def check_declarative_intent_params(want, module, result):
have = None
is_delay = False
for w in want:
if w.get('associated_interfaces') is None:
continue
if result['changed'] and not is_delay:
time.sleep(module.params['delay'])
is_delay = True
if have is None:
have = map_config_to_obj(module)
for i in w['associated_interfaces']:
obj_in_have = search_obj_in_list(w['vlan_id'], have)
if obj_in_have and 'interfaces' in obj_in_have and i not in obj_in_have['interfaces']:
module.fail_json(msg="Interface %s not configured on vlan %s" % (i, w['vlan_id']))
def main():
""" main entry point for module execution
"""
element_spec = dict(
vlan_id=dict(type='int'),
name=dict(),
interfaces=dict(type='list'),
associated_interfaces=dict(type='list'),
delay=dict(default=10, type='int'),
state=dict(default='present',
choices=['present', 'absent', 'active', 'suspend'])
)
aggregate_spec = deepcopy(element_spec)
aggregate_spec['vlan_id'] = dict(required=True)
# remove default in aggregate spec, to handle common arguments
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)
argument_spec.update(cnos_argument_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 = {'changed': False}
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
check_declarative_intent_params(want, module, result)
module.exit_json(**result)
if __name__ == '__main__':
main()

View file

@ -0,0 +1,369 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
#
# Copyright (C) 2019 Lenovo.
# (c) 2017, Ansible by Red Hat, inc
# This file is part of Ansible
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
#
# Module to work on management of local users on Lenovo CNOS Switches
# Lenovo Networking
#
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = '''
---
module: cnos_vrf
author: "Anil Kumar Muraleedharan (@amuraleedhar)"
short_description: Manage VRFs on Lenovo CNOS network devices
description:
- This module provides declarative management of VRFs
on Lenovo CNOS network devices.
notes:
- Tested against CNOS 10.9.1
options:
name:
description:
- Name of the VRF.
required: true
rd:
description:
- Route distinguisher of the VRF
interfaces:
description:
- Identifies the set of interfaces that
should be configured in the VRF. Interfaces must be routed
interfaces in order to be placed into a VRF. The name of interface
should be in expanded format and not abbreviated.
associated_interfaces:
description:
- This is a intent option and checks the operational state of the for
given vrf C(name) for associated interfaces. If the value in the
C(associated_interfaces) does not match with the operational state of
vrf interfaces on device it will result in failure.
aggregate:
description: List of VRFs contexts
purge:
description:
- Purge VRFs not defined in the I(aggregate) parameter.
default: no
type: bool
delay:
description:
- Time in seconds to wait before checking for the operational state on
remote device. This wait is applicable for operational state arguments.
default: 10
state:
description:
- State of the VRF configuration.
default: present
choices: ['present', 'absent']
'''
EXAMPLES = """
- name: Create vrf
cnos_vrf:
name: test
rd: 1:200
interfaces:
- Ethernet1/33
state: present
- name: Delete VRFs
cnos_vrf:
name: test
state: absent
- name: Create aggregate of VRFs with purge
cnos_vrf:
aggregate:
- { name: test4, rd: "1:204" }
- { name: test5, rd: "1:205" }
state: present
purge: yes
- name: Delete aggregate of VRFs
cnos_vrf:
aggregate:
- name: test2
- name: test3
- name: test4
- name: test5
state: absent
"""
RETURN = """
commands:
description: The list of configuration mode commands to send to the device
returned: always
type: list
sample:
- vrf context test
- rd 1:100
- interface Ethernet1/44
- vrf member test
"""
import re
import time
from copy import deepcopy
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import remove_default_spec
from ansible_collections.community.general.plugins.module_utils.network.cnos.cnos import load_config, run_commands
from ansible_collections.community.general.plugins.module_utils.network.cnos.cnos import cnos_argument_spec, check_args
def search_obj_in_list(name, lst):
for o in lst:
if o['name'] == name:
return o
def get_interface_type(interface):
intf_type = 'unknown'
if interface.upper()[:2] in ('ET', 'GI', 'FA', 'TE', 'FO', 'HU', 'TWE'):
intf_type = 'ethernet'
elif interface.upper().startswith('VL'):
intf_type = 'svi'
elif interface.upper().startswith('LO'):
intf_type = 'loopback'
elif interface.upper()[:2] in ('MG', 'MA'):
intf_type = 'management'
elif interface.upper().startswith('PO'):
intf_type = 'portchannel'
elif interface.upper().startswith('NV'):
intf_type = 'nve'
return intf_type
def is_switchport(name, module):
intf_type = get_interface_type(name)
if intf_type in ('ethernet', 'portchannel'):
config = run_commands(module,
['show interface {0} switchport'.format(name)])[0]
match = re.search(r'Switchport : enabled', config)
return bool(match)
return False
def map_obj_to_commands(updates, module):
commands = list()
want, have = updates
state = module.params['state']
purge = module.params['purge']
for w in want:
name = w['name']
rd = w['rd']
interfaces = w['interfaces']
obj_in_have = search_obj_in_list(name, have)
if name == 'default':
module.fail_json(msg='VRF context default is reserved')
elif len(name) > 63:
module.fail_json(msg='VRF name is too long')
if state == 'absent':
if name == 'management':
module.fail_json(msg='Management VRF context cannot be deleted')
if obj_in_have:
commands.append('no vrf context %s' % name)
elif state == 'present':
if not obj_in_have:
commands.append('vrf context %s' % name)
if rd is not None:
commands.append('rd %s' % rd)
if w['interfaces']:
for i in w['interfaces']:
commands.append('interface %s' % i)
commands.append('vrf member %s' % w['name'])
else:
if w['rd'] is not None and w['rd'] != obj_in_have['rd']:
commands.append('vrf context %s' % w['name'])
commands.append('rd %s' % w['rd'])
if w['interfaces']:
if not obj_in_have['interfaces']:
for i in w['interfaces']:
commands.append('interface %s' % i)
commands.append('vrf member %s' % w['name'])
elif set(w['interfaces']) != obj_in_have['interfaces']:
missing_interfaces = list(set(w['interfaces']) - set(obj_in_have['interfaces']))
for i in missing_interfaces:
commands.append('interface %s' % i)
commands.append('vrf member %s' % w['name'])
if purge:
for h in have:
obj_in_want = search_obj_in_list(h['name'], want)
if not obj_in_want:
commands.append('no vrf context %s' % h['name'])
return commands
def map_config_to_obj(module):
objs = []
output = run_commands(module, {'command': 'show vrf'})
if output is not None:
vrfText = output[0].strip()
vrfList = vrfText.split('VRF')
for vrfItem in vrfList:
if 'FIB ID' in vrfItem:
obj = dict()
list_of_words = vrfItem.split()
vrfName = list_of_words[0]
obj['name'] = vrfName[:-1]
obj['rd'] = list_of_words[list_of_words.index('RD') + 1]
start = False
obj['interfaces'] = []
for intName in list_of_words:
if 'Interfaces' in intName:
start = True
if start is True:
if '!' not in intName and 'Interfaces' not in intName:
obj['interfaces'].append(intName.strip().lower())
objs.append(obj)
else:
module.fail_json(msg='Could not fetch VRF details from device')
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]
if item.get('interfaces'):
item['interfaces'] = [intf.replace(" ", "").lower() for intf in item.get('interfaces') if intf]
if item.get('associated_interfaces'):
item['associated_interfaces'] = [intf.replace(" ", "").lower() for intf in item.get('associated_interfaces') if intf]
obj.append(item.copy())
else:
obj.append({
'name': module.params['name'],
'state': module.params['state'],
'rd': module.params['rd'],
'interfaces': [intf.replace(" ", "").lower() for intf in module.params['interfaces']] if module.params['interfaces'] else [],
'associated_interfaces': [intf.replace(" ", "").lower() for intf in
module.params['associated_interfaces']] if module.params['associated_interfaces'] else []
})
return obj
def check_declarative_intent_params(want, module, result):
have = None
is_delay = False
for w in want:
if w.get('associated_interfaces') is None:
continue
if result['changed'] and not is_delay:
time.sleep(module.params['delay'])
is_delay = True
if have is None:
have = map_config_to_obj(module)
for i in w['associated_interfaces']:
obj_in_have = search_obj_in_list(w['name'], have)
if obj_in_have:
interfaces = obj_in_have.get('interfaces')
if interfaces is not None and i not in interfaces:
module.fail_json(msg="Interface %s not configured on vrf %s" % (i, w['name']))
def main():
""" main entry point for module execution
"""
element_spec = dict(
name=dict(),
interfaces=dict(type='list'),
associated_interfaces=dict(type='list'),
delay=dict(default=10, type='int'),
rd=dict(),
state=dict(default='present', choices=['present', 'absent'])
)
aggregate_spec = deepcopy(element_spec)
# remove default in aggregate spec, to handle common arguments
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 = [['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()
check_args(module, warnings)
result = {'changed': False}
if warnings:
result['warnings'] = warnings
want = map_params_to_obj(module)
for w in want:
name = w['name']
name = name.lower()
if is_switchport(name, module):
module.fail_json(msg='Ensure interface is configured to be a L3'
'\nport first before using this module. You can use'
'\nthe cnos_interface module for this.')
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
check_declarative_intent_params(want, module, result)
module.exit_json(**result)
if __name__ == '__main__':
main()