Init: mediaserver

This commit is contained in:
2023-02-08 12:13:28 +01:00
parent 848bc9739c
commit f7c23d4ba9
31914 changed files with 6175775 additions and 0 deletions

View File

@@ -0,0 +1,97 @@
#
# (c) 2017 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/>.
#
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
DOCUMENTATION = '''
---
author: Egor Zaitsev (@heuels)
name: ciscosmb
short_description: Use ciscosmb cliconf to run command on Cisco SMB network devices
description:
- This ciscosmb plugin provides low level abstraction apis for
sending and receiving CLI commands from Cisco SMB network devices.
'''
import re
import json
from ansible.module_utils._text import to_text
from ansible.plugins.cliconf import CliconfBase, enable_mode
class Cliconf(CliconfBase):
def get_device_info(self):
device_info = {}
device_info['network_os'] = 'ciscosmb'
resource = self.get('show verison')
data = to_text(resource, errors='surrogate_or_strict').strip()
match = re.search(r'SW version +(\S+) \(.*$', data)
if match:
device_info['network_os_version'] = match.group(1)
model = self.get('show inventory')
data = to_text(model, errors='surrogate_or_strict').strip()
match = re.search(r'PID: (.+)$', data, re.M)
if match:
device_info['network_os_model'] = match.group(1)
identity = self.get('show system')
data = to_text(identity, errors='surrogate_or_strict').strip()
match = re.search(r'System Name: +(\S+)', data, re.M)
if match:
device_info['network_os_hostname'] = match.group(1)
return device_info
@enable_mode
def get_config(self, source='running', flags=None, format=None):
if source not in ("running", "startup"):
raise ValueError(
"fetching configuration from %s is not supported" % source
)
if format:
raise ValueError(
"'format' value %s is not supported for get_config" % format
)
if flags:
raise ValueError(
"'flags' value %s is not supported for get_config" % flags
)
if source == "running":
cmd = "show running-config "
else:
cmd = "show startup-config "
return self.send_command(cmd)
def edit_config(self, command):
return
def get(self, command, prompt=None, answer=None, sendonly=False, newline=True, check_all=False):
return self.send_command(command=command, prompt=prompt, answer=answer, sendonly=sendonly, newline=newline, check_all=check_all)
def get_capabilities(self):
result = super().get_capabilities()
return json.dumps(result)

View File

@@ -0,0 +1,302 @@
# This code is part of Ansible, but is an independent component.
# This particular file snippet, and this file snippet only, is BSD licensed.
# Modules you write using this snippet, which is embedded dynamically by Ansible
# still belong to the author of the module, and may assign their own license
# to the complete work.
#
# (c) 2016 Red Hat Inc.
#
# Redistribution and use in source and binary forms, with or without modification,
# are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
# IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
# USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
from __future__ import absolute_import, division, print_function
__metaclass__ = type
import json
import re
from ansible.module_utils._text import to_text, to_native
from ansible.module_utils.basic import env_fallback
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import to_list, ComplexList
from ansible.module_utils.connection import Connection, ConnectionError
# copy of https://github.com/napalm-automation/napalm/blob/develop/napalm/base/canonical_map.py
from ansible_collections.community.ciscosmb.plugins.module_utils.ciscosmb_canonical_map import base_interfaces
_DEVICE_CONFIGS = {}
ciscosmb_provider_spec = {
'host': dict(),
'port': dict(type='int'),
'username': dict(fallback=(env_fallback, ['ANSIBLE_NET_USERNAME'])),
'password': dict(fallback=(env_fallback, ['ANSIBLE_NET_PASSWORD']), no_log=True),
'ssh_keyfile': dict(fallback=(env_fallback, ['ANSIBLE_NET_SSH_KEYFILE']), type='path'),
'timeout': dict(type='int')
}
ciscosmb_argument_spec = {}
def ciscosmb_split_to_tables(data):
TABLE_HEADER = re.compile(r"^---+ +-+.*$")
EMPTY_LINE = re.compile(r"^ *$")
tables = dict()
tableno = -1
lineno = 0
tabledataget = False
for line in data.splitlines():
if re.match(EMPTY_LINE, line):
tabledataget = False
continue
if re.match(TABLE_HEADER, line):
tableno += 1
tabledataget = True
lineno = 0
tables[tableno] = dict()
tables[tableno]["header"] = line
tables[tableno]["data"] = dict()
continue
if tabledataget:
tables[tableno]["data"][lineno] = line
lineno += 1
continue
return tables
def ciscosmb_parse_table(table, allow_overflow=True, allow_empty_fields=None):
if allow_empty_fields is None:
allow_empty_fields = list()
fields_end = __get_table_columns_end(table["header"])
data = __get_table_data(
table["data"], fields_end, allow_overflow, allow_empty_fields
)
return data
def __get_table_columns_end(headerline):
""" fields length are diferent device to device, detect them on horizontal lin """
fields_end = [m.start() for m in re.finditer(" *", headerline.strip())]
# fields_position.insert(0,0)
# fields_end.append(len(headerline))
fields_end.append(10000) # allow "long" last field
return fields_end
def __line_to_fields(line, fields_end):
""" dynamic fields lenghts """
line_elems = {}
index = 0
f_start = 0
for f_end in fields_end:
line_elems[index] = line[f_start:f_end].strip()
index += 1
f_start = f_end
return line_elems
def __get_table_data(
tabledata, fields_end, allow_overflow=True, allow_empty_fields=None
):
if allow_empty_fields is None:
allow_empty_fields = list()
data = dict()
dataindex = 0
for lineno in tabledata:
owerflownfields = list()
owerflow = False
line = tabledata[lineno]
line_elems = __line_to_fields(line, fields_end)
if allow_overflow:
# search for overflown fields
for elemno in line_elems:
if elemno not in allow_empty_fields and line_elems[elemno] == "":
owerflow = True
else:
owerflownfields.append(elemno)
if owerflow:
# concat owerflown elements to previous data
for fieldno in owerflownfields:
data[dataindex - 1][fieldno] += line_elems[fieldno]
else:
data[dataindex] = line_elems
dataindex += 1
else:
data[dataindex] = line_elems
dataindex += 1
return data
def ciscosmb_merge_dicts(a, b, path=None):
"merges b into a"
if path is None:
path = []
# is b empty?
if not bool(b):
return a
for key in b:
if key in a:
if isinstance(a[key], dict) and isinstance(b[key], dict):
ciscosmb_merge_dicts(a[key], b[key], path + [str(key)])
elif a[key] == b[key]:
pass # same leaf value
else:
raise Exception("Conflict at %s" % ".".join(path + [str(key)]))
else:
a[key] = b[key]
return a
def interface_canonical_name(interface):
iftype = interface.rstrip(r"/\0123456789. ")
ifno = interface[len(iftype):].lstrip()
if iftype in base_interfaces:
iftype = base_interfaces[iftype]
interface = iftype + str(ifno)
return interface
def get_provider_argspec():
return ciscosmb_provider_spec
def get_connection(module):
if hasattr(module, '_ciscosmb_connection'):
return module._ciscosmb_connection
capabilities = get_capabilities(module)
network_api = capabilities.get('network_api')
if network_api == 'cliconf':
module._ciscosmb_connection = Connection(module._socket_path)
else:
module.fail_json(msg='Invalid connection type %s' % network_api)
return module._ciscosmb_connection
def get_capabilities(module):
if hasattr(module, '_ciscosmb_capabilities'):
return module._ciscosmb_capabilities
try:
capabilities = Connection(module._socket_path).get_capabilities()
module._ciscosmb_capabilities = json.loads(capabilities)
return module._ciscosmb_capabilities
except ConnectionError as exc:
module.fail_json(msg=to_native(exc, errors='surrogate_then_replace'))
def get_defaults_flag(module):
connection = get_connection(module)
try:
out = connection.get('/system default-configuration print')
except ConnectionError as exc:
module.fail_json(msg=to_text(exc, errors='surrogate_then_replace'))
out = to_text(out, errors='surrogate_then_replace')
commands = set()
for line in out.splitlines():
if line.strip():
commands.add(line.strip().split()[0])
if 'all' in commands:
return ['all']
else:
return ['full']
def get_config(module, flags=None):
flag_str = ' '.join(to_list(flags))
try:
return _DEVICE_CONFIGS[flag_str]
except KeyError:
connection = get_connection(module)
try:
out = connection.get_config(flags=flags)
except ConnectionError as exc:
module.fail_json(msg=to_text(exc, errors='surrogate_then_replace'))
cfg = to_text(out, errors='surrogate_then_replace').strip()
_DEVICE_CONFIGS[flag_str] = cfg
return cfg
def to_commands(module, commands):
spec = {
'command': dict(key=True),
'prompt': dict(),
'answer': dict()
}
transform = ComplexList(spec, module)
return transform(commands)
def run_commands(module, commands, check_rc=True):
responses = list()
connection = get_connection(module)
for cmd in to_list(commands):
if isinstance(cmd, dict):
command = cmd['command']
prompt = cmd['prompt']
answer = cmd['answer']
else:
command = cmd
prompt = None
answer = None
try:
out = connection.get(command, prompt, answer)
except ConnectionError as exc:
module.fail_json(msg=to_text(exc, errors='surrogate_then_replace'))
try:
out = to_text(out, errors='surrogate_or_strict')
except UnicodeError:
module.fail_json(
msg=u'Failed to decode output from %s: %s' % (cmd, to_text(out)))
responses.append(out)
return responses

View File

@@ -0,0 +1,165 @@
# The contents of this file are licensed under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with the
# License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations under
# the License.
base_interfaces = {
"ATM": "ATM",
"AT": "ATM",
"B": "Bdi",
"Bd": "Bdi",
"Bdi": "Bdi",
"EOBC": "EOBC",
"EO": "EOBC",
"Ethernet": "Ethernet",
"Eth": "Ethernet",
"eth": "Ethernet",
"Et": "Ethernet",
"et": "Ethernet",
"FastEthernet": "FastEthernet",
"FastEth": "FastEthernet",
"FastE": "FastEthernet",
"Fast": "FastEthernet",
"Fas": "FastEthernet",
"FE": "FastEthernet",
"Fa": "FastEthernet",
"fa": "FastEthernet",
"Fddi": "Fddi",
"FD": "Fddi",
"FortyGigabitEthernet": "FortyGigabitEthernet",
"FortyGigEthernet": "FortyGigabitEthernet",
"FortyGigEth": "FortyGigabitEthernet",
"FortyGigE": "FortyGigabitEthernet",
"FortyGig": "FortyGigabitEthernet",
"FGE": "FortyGigabitEthernet",
"FO": "FortyGigabitEthernet",
"Fo": "FortyGigabitEthernet",
"FiftyGigabitEthernet": "FiftyGigabitEthernet",
"FiftyGigEthernet": "FiftyGigabitEthernet",
"FiftyGigEth": "FiftyGigabitEthernet",
"FiftyGigE": "FiftyGigabitEthernet",
"FI": "FiftyGigabitEthernet",
"Fi": "FiftyGigabitEthernet",
"fi": "FiftyGigabitEthernet",
"GigabitEthernet": "GigabitEthernet",
"GigEthernet": "GigabitEthernet",
"GigEth": "GigabitEthernet",
"GigE": "GigabitEthernet",
"Gig": "GigabitEthernet",
"GE": "GigabitEthernet",
"Ge": "GigabitEthernet",
"ge": "GigabitEthernet",
"Gi": "GigabitEthernet",
"gi": "GigabitEthernet",
"HundredGigabitEthernet": "HundredGigabitEthernet",
"HundredGigEthernet": "HundredGigabitEthernet",
"HundredGigEth": "HundredGigabitEthernet",
"HundredGigE": "HundredGigabitEthernet",
"HundredGig": "HundredGigabitEthernet",
"Hu": "HundredGigabitEthernet",
"TwentyFiveGigabitEthernet": "TwentyFiveGigabitEthernet",
"TwentyFiveGigEthernet": "TwentyFiveGigabitEthernet",
"TwentyFiveGigEth": "TwentyFiveGigabitEthernet",
"TwentyFiveGigE": "TwentyFiveGigabitEthernet",
"TwentyFiveGig": "TwentyFiveGigabitEthernet",
"TF": "TwentyFiveGigabitEthernet",
"Tf": "TwentyFiveGigabitEthernet",
"tf": "TwentyFiveGigabitEthernet",
"TwoHundredGigabitEthernet": "TwoHundredGigabitEthernet",
"TwoHundredGigEthernet": "TwoHundredGigabitEthernet",
"TwoHundredGigEth": "TwoHundredGigabitEthernet",
"TwoHundredGigE": "TwoHundredGigabitEthernet",
"TwoHundredGig": "TwoHundredGigabitEthernet",
"TH": "TwoHundredGigabitEthernet",
"Th": "TwoHundredGigabitEthernet",
"th": "TwoHundredGigabitEthernet",
"FourHundredGigabitEthernet": "FourHundredGigabitEthernet",
"FourHundredGigEthernet": "FourHundredGigabitEthernet",
"FourHundredGigEth": "FourHundredGigabitEthernet",
"FourHundredGigE": "FourHundredGigabitEthernet",
"FourHundredGig": "FourHundredGigabitEthernet",
"F": "FourHundredGigabitEthernet",
"f": "FourHundredGigabitEthernet",
"Loopback": "Loopback",
"loopback": "Loopback",
"Lo": "Loopback",
"lo": "Loopback",
"Management": "Management",
"Mgmt": "Management",
"mgmt": "Management",
"Ma": "Management",
"Management_short": "Ma",
"MFR": "MFR",
"Multilink": "Multilink",
"Mu": "Multilink",
"n": "nve",
"nv": "nve",
"nve": "nve",
"PortChannel": "Port-channel",
"Port-channel": "Port-channel",
"Port-Channel": "Port-channel",
"port-channel": "Port-channel",
"po": "Port-channel",
"Po": "Port-channel",
"POS": "POS",
"PO": "POS",
"Serial": "Serial",
"Se": "Serial",
"S": "Serial",
"TenGigabitEthernet": "TenGigabitEthernet",
"TenGigEthernet": "TenGigabitEthernet",
"TenGigEth": "TenGigabitEthernet",
"TenGig": "TenGigabitEthernet",
"TeGig": "TenGigabitEthernet",
"Ten": "TenGigabitEthernet",
"T": "TenGigabitEthernet",
"Te": "TenGigabitEthernet",
"te": "TenGigabitEthernet",
"Tunnel": "Tunnel",
"Tun": "Tunnel",
"Tu": "Tunnel",
"Twe": "TwentyFiveGigE",
"Tw": "TwoGigabitEthernet",
"Two": "TwoGigabitEthernet",
"Virtual-Access": "Virtual-Access",
"Vi": "Virtual-Access",
"Virtual-Template": "Virtual-Template",
"Vt": "Virtual-Template",
"VLAN": "VLAN",
"V": "VLAN",
"Vl": "VLAN",
"Wlan-GigabitEthernet": "Wlan-GigabitEthernet",
}
reverse_mapping = {
"ATM": "At",
"EOBC": "EO",
"Ethernet": "Et",
"FastEthernet": "Fa",
"Fddi": "FD",
"FortyGigabitEthernet": "Fo",
"GigabitEthernet": "Gi",
"HundredGigabitEthernet": "Hu",
"Loopback": "Lo",
"Management": "Ma",
"MFR": "MFR",
"Multilink": "Mu",
"Port-channel": "Po",
"POS": "PO",
"Serial": "Se",
"TenGigabitEthernet": "Te",
"Tunnel": "Tu",
"TwoGigabitEthernet": "Two",
"TwentyFiveGigE": "Twe",
"Virtual-Access": "Vi",
"Virtual-Template": "Vt",
"VLAN": "Vl",
"Wlan-GigabitEthernet": "Wl-Gi",
}

View File

@@ -0,0 +1,185 @@
#!/usr/bin/python
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
DOCUMENTATION = '''
---
module: command
author: "Petr Klima (@qaxi)"
short_description: Run commands on remote Cisco SMB devices
description:
- Sends arbitrary commands to an Cisco SMB node and returns the results
read from the device. This module includes an
argument that will cause the module to wait for a specific condition
before returning or timing out if the condition is not met.
options:
commands:
description:
- List of commands to send to the remote Cisco SMB device over the
configured provider. The resulting output from the command
is returned. If the I(wait_for) argument is provided, the
module is not returned until the condition is satisfied or
the number of retries has expired.
required: true
type: list
elements: str
wait_for:
description:
- List of conditions to evaluate against the output of the
command. The task will wait for each condition to be true
before moving forward. If the conditional is not true
within the configured number of retries, the task fails.
See examples.
type: list
elements: str
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
type: str
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
type: int
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
type: int
'''
EXAMPLES = """
- name: Run command on remote devices
community.ciscosmb.command:
commands: show version
- name: Run command and check to see if output contains PID
community.ciscosmb.command:
commands: show inventory
wait_for: result[0] contains PID
- name: Run multiple commands on remote nodes
community.ciscosmb.command:
commands:
- show version
- show system
- name: Run multiple commands and evaluate the output
community.ciscosmb.command:
commands:
- show version
- show system
wait_for:
- result[0] contains Active-image
- result[1] contains "System Up Time"
"""
RETURN = """
stdout:
description: The set of responses from the commands.
returned: always apart from low level errors (such as action plugin)
type: list
sample: ['...', '...']
stdout_lines:
description: The value of stdout split into a list.
returned: always apart from low level errors (such as action plugin)
type: list
sample: [['...', '...'], ['...'], ['...']]
failed_conditions:
description: The list of conditionals that have failed.
returned: failed
type: list
sample: ['...', '...']
"""
import time
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.parsing import Conditional
from ansible_collections.community.ciscosmb.plugins.module_utils.ciscosmb import run_commands
from ansible_collections.community.ciscosmb.plugins.module_utils.ciscosmb import ciscosmb_argument_spec
from ansible.module_utils.basic import AnsibleModule
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():
"""main entry point for module execution
"""
argument_spec = dict(
commands=dict(type='list', elements='str', required=True),
wait_for=dict(type='list', elements='str'),
match=dict(default='all', choices=['all', 'any']),
retries=dict(default=10, type='int'),
interval=dict(default=1, type='int')
)
argument_spec.update(ciscosmb_argument_spec)
module = AnsibleModule(argument_spec=argument_spec,
supports_check_mode=False)
result = {'changed': False}
wait_for = module.params['wait_for'] or list()
conditionals = [Conditional(c) for c in wait_for]
retries = module.params['retries']
interval = module.params['interval']
match = module.params['match']
while retries > 0:
responses = run_commands(module, module.params['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,763 @@
#!/usr/bin/python
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
DOCUMENTATION = """
---
module: facts
author: "Petr Klima (@qaxi)"
short_description: Collect facts from remote devices running Cisco SMB
description:
- Collects a base set of device facts from a remote device that
is running Cisco SMB. 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.
options:
gather_subset:
description:
- When supplied, this argument will restrict the facts collected
to a given subset. Possible values for this argument include
C(all), C(hardware), C(config) and C(interfaces). Can specify a list of
values to include a larger subset. Values can also be used
with an initial C(!) to specify that a specific subset should
not be collected.
required: false
type: list
elements: str
choices: [ 'default', 'all', 'hardware', 'config', 'interfaces', '!hardware', '!config', '!interfaces' ]
default: '!config'
notes:
- Supports C(check_mode).
"""
EXAMPLES = """
- name: Collect all facts from the device
community.ciscosmb.facts:
gather_subset: all
- name: Collect only the config and default facts
community.ciscosmb.facts:
gather_subset:
- config
- name: Do not collect hardware facts
community.ciscosmb.facts:
gather_subset:
- "!hardware"
"""
RETURN = """
ansible_net_gather_subset:
description: The list of fact subsets collected from the device.
returned: always
type: list
# default
ansible_net_model:
description: The model name returned from the device.
returned: always
type: str
ansible_net_serialnum:
description: The serial number of the remote device.
returned: always
type: str
ansible_net_version:
description: The operating system version running on the remote device.
returned: always
type: str
ansible_net_hostname:
description: The configured hostname of the device.
returned: always
type: str
ansible_net_uptime:
description: The uptime of the device.
returned: always
type: str
ansible_net_cpu_load:
description: Current CPU load.
returned: always
type: str
ansible_net_stacked_models:
description: The model names of each device in the stack.
returned: when multiple devices are configured in a stack
type: list
ansible_net_stacked_serialnums:
description: The serial numbers of each device in the stack.
returned: when multiple devices are configured in a stack
type: list
# hardware
ansible_net_spacefree_mb:
description: The available disk space on the remote device in MiB.
returned: when hardware is configured
type: dict
ansible_net_spacetotal_mb:
description: The total disk space on the remote device in MiB.
returned: when hardware is configured
type: dict
ansible_net_memfree_mb:
description: The available free memory on the remote device in MiB.
returned: when hardware is configured
type: int
ansible_net_memtotal_mb:
description: The total memory on the remote device in MiB.
returned: when hardware is configured
type: int
# config
ansible_net_config:
description: The current active config from the device.
returned: when config is configured
type: str
# interfaces
ansible_net_all_ipv4_addresses:
description: All IPv4 addresses configured on the device.
returned: when interfaces is configured
type: list
ansible_net_all_ipv6_addresses:
description: All IPv6 addresses configured on the device.
returned: when interfaces is configured
type: list
ansible_net_interfaces:
description: A hash of all interfaces running on the system.
returned: when interfaces is configured
type: dict
ansible_net_neighbors:
description: The list of neighbors from the remote device.
returned: when interfaces is configured
type: dict
"""
import re
from ansible_collections.community.ciscosmb.plugins.module_utils.ciscosmb import (
run_commands,
ciscosmb_argument_spec,
interface_canonical_name,
ciscosmb_split_to_tables,
ciscosmb_parse_table,
ciscosmb_merge_dicts,
)
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.six import iteritems
class FactsBase(object):
COMMANDS = list()
def __init__(self, module):
self.module = module
self.facts = dict()
self.responses = None
def populate(self):
self.responses = run_commands(
self.module, commands=self.COMMANDS, check_rc=False
)
def run(self, cmd):
return run_commands(self.module, commands=cmd, check_rc=False)
class Default(FactsBase):
COMMANDS = [
"show version",
"show system",
"show cpu utilization",
"show inventory",
]
def populate(self):
super(Default, self).populate()
data = self.responses[0]
if data:
self.facts["version"] = self.parse_version(data)
self.facts["boot_version"] = self.parse_boot_version(data)
data = self.responses[1]
if data:
self.facts["uptime"] = self.parse_uptime(data)
self.facts["hostname"] = self.parse_hostname(data)
data = self.responses[2]
if data:
self.facts["cpu_load"] = self.parse_cpu_load(data)
data = self.responses[3]
if data:
modules = self.parse_inventory(data)
stacked_models = self.parse_stacked_models(modules)
if len(stacked_models) >= 2:
stacked_serialnums = self.parse_stacked_serialnums(modules)
self.facts["stacked_models"] = stacked_models
self.facts["stacked_serialnums"] = stacked_serialnums
self.facts["model"] = self.parse_model(modules)
self.facts["serialnum"] = self.parse_serialnum(modules)
self.facts["hw_version"] = self.parse_hw_version(modules)
self.facts["hw_modules"] = modules
# show version
def parse_version(self, data):
# Cisco SMB 300 and 500 - fw 1.x.x.x
match = re.search(r"^SW version\s*(\S+)\s*.*$", data, re.M)
if match:
return match.group(1)
# Cisco SMB 350 and 550 - fw 2.x.x.x
match = re.search(r"^ Version:\s*(\S+)\s*.*$", data, re.M)
if match:
return match.group(1)
def parse_boot_version(self, data):
match = re.search(r"Boot version\s*(\S+)\s*.*$", data, re.M)
if match:
return match.group(1)
# show system
def parse_uptime(self, data):
match = re.search(r"^System Up Time \S+:\s+(\S+)\s*$", data, re.M)
if match:
(dayhour, mins, sec) = match.group(1).split(":")
(day, hour) = dayhour.split(",")
# output in seconds
return (int(day) * 86400) + (int(hour) * 3600) + (int(mins) * 60) + int(sec)
def parse_hostname(self, data):
match = re.search(r"^System Name:\s*(\S+)\s*$", data, re.M)
if match:
return match.group(1)
# show cpu utilization
def parse_cpu_load(self, data):
match = re.search(r"one minute:\s+(\d+)%;\s*", data, re.M)
if match:
return match.group(1)
# show inventory
def parse_inventory(self, data):
# make 1 module 1 line
data = re.sub(r"\nPID", " PID", data)
# delete empty lines
data = re.sub(r"^\n", "", data)
data = re.sub(r"\n\n", "", data)
data = re.sub(r"\n\s*\n", r"\n", data)
lines = data.splitlines()
modules = {}
for line in lines:
# remove extra chars
line = re.sub(r'"', r"", line)
line = re.sub(r"\s+", r" ", line)
# normalize lines
line = re.sub(r":\s", r'"', line)
line = re.sub(r'\s+DESCR"', r'"DESCR"', line)
line = re.sub(r'\s+PID"', r'"PID"', line)
line = re.sub(r'\s+VID"', r'"VID"', line)
line = re.sub(r'\s+SN"', r'"SN"', line)
line = re.sub(r"\s*$", r"", line)
match = re.search(
r'^NAME"(?P<name>[^"]+)"DESCR"(?P<descr>[^"]+)"PID"(?P<pid>[^"]+)"VID"(?P<vid>[^"]+)"SN"(?P<sn>\S+)\s*',
line,
)
modul = match.groupdict()
modules[modul["name"]] = modul
if modules:
return modules
def parse_stacked_models(self, data):
# every inventory has module with NAME: "1"
# stacks have modules 2 3 ... 8
models = []
for n in range(1, 9):
# index is string
if str(n) in data:
models.append(data[str(n)]["pid"])
return models
def parse_stacked_serialnums(self, data):
# every inventory has module with NAME: "1"
# stacks have modules 2 3 ... 8
sn = []
for n in range(1, 9):
# index is string
if str(n) in data:
sn.append(data[str(n)]["sn"])
return sn
def parse_model(self, data):
# every inventory has module with NAME: "1"
model = data["1"]["pid"]
if "stacked_models" in self.facts:
model = re.sub(r"-.*$", "", model)
model = "Stack " + model
return model
def parse_serialnum(self, data):
# every inventory has module with NAME: "1"
sn = data["1"]["sn"]
return sn
def parse_hw_version(self, data):
# every inventory has module with NAME: "1"
sn = data["1"]["vid"]
return sn
class Hardware(FactsBase):
COMMANDS = [
"dir",
]
def populate(self):
super(Hardware, self).populate()
data = self.responses[0]
if data:
self.parse_filesystem_info(data)
def parse_filesystem_info(self, data):
match = re.search(r"Total size of (\S+): (\d+) bytes", data, re.M)
if match: # fw 1.x
self.facts["spacetotal_mb"] = round(int(match.group(2)) / 1024 / 1024, 1)
match = re.search(r"Free size of (\S+): (\d+) bytes", data, re.M)
self.facts["spacefree_mb"] = round(int(match.group(2)) / 1024 / 1024, 1)
else:
match = re.search(r"(\d+)K of (\d+)K are free", data, re.M)
if match: # fw 2.x, 3.x
self.facts["spacetotal_mb"] = round(int(match.group(2)) / 1024, 1)
self.facts["spacefree_mb"] = round(int(match.group(1)) / 1024, 1)
class Config(FactsBase):
COMMANDS = ["show running-config detailed"]
def populate(self):
super(Config, self).populate()
data = self.responses[0]
if data:
self.facts["config"] = data
class Interfaces(FactsBase):
COMMANDS = [
"show ports jumbo-frame",
"show ip interface",
"show ipv6 interface brief",
"show interfaces status",
"show interfaces configuration",
"show interfaces description",
"show lldp neighbors",
]
DETAIL_RE = re.compile(
r"([\w\d\-]+)=\"?(\w{3}/\d{2}/\d{4}\s\d{2}:\d{2}:\d{2}|[\w\d\-\.:/]+)"
)
WRAPPED_LINE_RE = re.compile(r"^\s+(?!\d)")
def populate(self):
super(Interfaces, self).populate()
self.facts["interfaces"] = dict()
self.facts["all_ipv4_addresses"] = list()
self.facts["all_ipv6_addresses"] = list()
self.facts["neighbors"] = list()
data = self.responses[0]
if data:
self.populate_interfaces_mtu(data)
data = self.responses[1]
if data:
self.populate_addresses_ipv4(data)
data = self.responses[2]
if data:
self.populate_addresses_ipv6(data)
data = self.responses[3]
if data:
self.populate_interfaces_status(data)
data = self.responses[4]
if data:
self.populate_interfaces_configuration(data)
data = self.responses[5]
if data:
self.populate_interfaces_description(data)
data = self.responses[6]
if data:
self.populate_neighbors(data)
def _populate_interfaces_status_interface(self, interface_table):
interfaces = dict()
for key in interface_table:
i = interface_table[key]
interface = dict()
interface["state"] = i[6].lower()
interface["type"] = i[1]
interface["mtu"] = self._mtu
interface["duplex"] = i[2].lower()
interface["negotiation"] = i[4].lower()
interface["control"] = i[5].lower()
interface["presure"] = i[7].lower()
interface["mode"] = i[8].lower()
if i[6] == "Up":
interface["bandwith"] = int(i[3]) * 1000 # to get speed in kb
else:
interface["bandwith"] = None
for key in interface:
if interface[key] == "--":
interface[key] = None
interfaces[interface_canonical_name(i[0])] = interface
return interfaces
def _populate_interfaces_status_portchanel(self, interface_table):
interfaces = dict()
for key in interface_table:
interface = dict()
i = interface_table[key]
interface["state"] = i[6].lower()
interface["type"] = i[1]
interface["mtu"] = self._mtu
interface["duplex"] = i[2].lower()
interface["negotiation"] = i[4].lower()
interface["control"] = i[5].lower()
if i[6] == "Up":
interface["bandwith"] = int(i[3]) * 1000 # to get speed in kb
else:
interface["bandwith"] = None
for key in interface:
if interface[key] == "--":
interface[key] = None
interfaces[interface_canonical_name(i[0])] = interface
return interfaces
def populate_interfaces_status(self, data):
tables = ciscosmb_split_to_tables(data)
interface_table = ciscosmb_parse_table(tables[0])
portchanel_table = ciscosmb_parse_table(tables[1])
interfaces = self._populate_interfaces_status_interface(interface_table)
self.facts["interfaces"] = ciscosmb_merge_dicts(
self.facts["interfaces"], interfaces
)
interfaces = self._populate_interfaces_status_portchanel(portchanel_table)
self.facts["interfaces"] = ciscosmb_merge_dicts(
self.facts["interfaces"], interfaces
)
def _populate_interfaces_configuration_interface(self, interface_table):
interfaces = dict()
for key in interface_table:
i = interface_table[key]
interface = dict()
interface["admin_state"] = i[6].lower()
interface["mdix"] = i[8].lower()
interfaces[interface_canonical_name(i[0])] = interface
return interfaces
def _populate_interfaces_configuration_portchanel(self, interface_table):
interfaces = dict()
for key in interface_table:
interface = dict()
i = interface_table[key]
interface["admin_state"] = i[5].lower()
interfaces[interface_canonical_name(i[0])] = interface
return interfaces
def populate_interfaces_configuration(self, data):
tables = ciscosmb_split_to_tables(data)
interface_table = ciscosmb_parse_table(tables[0])
portchanel_table = ciscosmb_parse_table(tables[1])
interfaces = self._populate_interfaces_configuration_interface(interface_table)
self.facts["interfaces"] = ciscosmb_merge_dicts(
self.facts["interfaces"], interfaces
)
interfaces = self._populate_interfaces_configuration_portchanel(
portchanel_table
)
self.facts["interfaces"] = ciscosmb_merge_dicts(
self.facts["interfaces"], interfaces
)
def _populate_interfaces_description_interface(self, interface_table):
interfaces = dict()
for key in interface_table:
i = interface_table[key]
interface = dict()
interface["description"] = i[1]
if interface["description"] == "":
interface["description"] = None
interfaces[interface_canonical_name(i[0])] = interface
return interfaces
def _populate_interfaces_description_portchanel(self, interface_table):
interfaces = dict()
for key in interface_table:
interface = dict()
i = interface_table[key]
interface["description"] = i[1]
if interface["description"] == "":
interface["description"] = None
interfaces[interface_canonical_name(i[0])] = interface
return interfaces
def populate_interfaces_description(self, data):
tables = ciscosmb_split_to_tables(data)
interface_table = ciscosmb_parse_table(tables[0], False)
portchanel_table = ciscosmb_parse_table(tables[1], False)
interfaces = self._populate_interfaces_description_interface(interface_table)
self.facts["interfaces"] = ciscosmb_merge_dicts(
self.facts["interfaces"], interfaces
)
interfaces = self._populate_interfaces_description_portchanel(portchanel_table)
self.facts["interfaces"] = ciscosmb_merge_dicts(
self.facts["interfaces"], interfaces
)
def _populate_address_ipv4(self, ip_table):
ips = list()
for key in ip_table:
cidr = ip_table[key][0]
interface = interface_canonical_name(ip_table[key][1])
ip, mask = cidr.split("/")
ips.append(ip)
# add ips to interface
self._new_interface(interface)
if "ipv4" not in self.facts["interfaces"][interface]:
self.facts["interfaces"][interface]["ipv4"] = list()
self.facts["interfaces"][interface]["ipv4"].append(
dict(address=ip, subnet=mask)
)
return ips
def populate_addresses_ipv4(self, data):
tables = ciscosmb_split_to_tables(data)
ip_table = ciscosmb_parse_table(tables[0])
ips = self._populate_address_ipv4(ip_table)
self.facts["all_ipv4_addresses"] = ips
def _populate_address_ipv6(self, ip_table):
ips = list()
for key in ip_table:
ip = ip_table[key][3]
interface = interface_canonical_name(ip_table[key][0])
ips.append(ip)
# add ips to interface
self._new_interface(interface)
if "ipv6" not in self.facts["interfaces"][interface]:
self.facts["interfaces"][interface]["ipv6"] = list()
self.facts["interfaces"][interface]["ipv6"].append(dict(address=ip))
return ips
def _new_interface(self, interface):
if interface in self.facts["interfaces"]:
return
else:
self.facts["interfaces"][interface] = dict()
self.facts["interfaces"][interface]["mtu"] = self._mtu
self.facts["interfaces"][interface]["admin_state"] = "up"
self.facts["interfaces"][interface]["description"] = None
self.facts["interfaces"][interface]["state"] = "up"
self.facts["interfaces"][interface]["bandwith"] = None
self.facts["interfaces"][interface]["duplex"] = None
self.facts["interfaces"][interface]["negotiation"] = None
self.facts["interfaces"][interface]["control"] = None
return
def populate_addresses_ipv6(self, data):
tables = ciscosmb_split_to_tables(data)
ip_table = ciscosmb_parse_table(tables[0])
ips = self._populate_address_ipv6(ip_table)
self.facts["all_ipv6_addresses"] = ips
def populate_interfaces_mtu(self, data):
# by documentation SG350
match = re.search(r"Jumbo frames are enabled", data, re.M)
if match:
mtu = 9000
else:
mtu = 1518
self._mtu = mtu
def populate_neighbors(self, data):
tables = ciscosmb_split_to_tables(data)
neighbor_table = ciscosmb_parse_table(tables[0], allow_empty_fields=[3])
neighbors = dict()
for key in neighbor_table:
neighbor = neighbor_table[key]
ifcname = interface_canonical_name(neighbor[0])
host = neighbor[3]
port = neighbor[2]
hostport = {"host": host, "port": port}
if ifcname not in neighbors:
neighbors[ifcname] = list()
neighbors[ifcname].append(hostport)
self.facts["neighbors"] = neighbors
FACT_SUBSETS = dict(
default=Default,
hardware=Hardware,
interfaces=Interfaces,
config=Config,
)
VALID_SUBSETS = frozenset(FACT_SUBSETS.keys())
warnings = list()
def main():
"""main entry point for module execution"""
argument_spec = dict(
gather_subset=dict(
default=["!config"],
type="list",
elements="str",
choices=[
"all",
"default",
"hardware",
"interfaces",
"config",
"!hardware",
"!interfaces",
"!config",
],
)
)
argument_spec.update(ciscosmb_argument_spec)
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: %s" % subset)
if exclude:
exclude_subsets.add(subset)
else:
runable_subsets.add(subset)
if not runable_subsets:
runable_subsets.update(VALID_SUBSETS)
runable_subsets.difference_update(exclude_subsets)
runable_subsets.add("default")
facts = dict()
facts["gather_subset"] = list(runable_subsets)
instances = list()
for key in runable_subsets:
instances.append(FACT_SUBSETS[key](module))
for inst in instances:
inst.populate()
facts.update(inst.facts)
ansible_facts = dict()
for key, value in iteritems(facts):
key = "ansible_net_%s" % key
ansible_facts[key] = value
module.exit_json(ansible_facts=ansible_facts, warnings=warnings)
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,123 @@
#
# (c) 2016 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/>.
#
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
# Py 2.7 compat.
from ansible.module_utils.six import raise_from
import json
import re
from ansible.errors import AnsibleConnectionFailure
from ansible.module_utils._text import to_text, to_bytes
from ansible.plugins.terminal import TerminalBase
from ansible.utils.display import Display
display = Display()
class TerminalModule(TerminalBase):
# https://docs.ansible.com/ansible/latest/collections/ansible/netcommon/network_cli_connection.html
terminal_stdout_re = [
re.compile(br"[\r\n]?[\w\+\-\.:\/\[\]]+(?:\([^\)]+\)){0,3}(?:[>#]) ?$")
]
terminal_stderr_re = [
re.compile(br"% ?Error"),
re.compile(br"^% \w+", re.M),
re.compile(br"% ?Bad secret"),
re.compile(br"[\r\n%] Bad passwords"),
re.compile(br"invalid input", re.I),
re.compile(br"(?:incomplete|ambiguous) command", re.I),
re.compile(br"connection timed out", re.I),
re.compile(br"[^\r\n]+ not found"),
re.compile(br"'[^']' +returned error code: ?\d+"),
re.compile(br"Bad mask", re.I),
re.compile(br"% ?(\S+) ?overlaps with ?(\S+)", re.I),
re.compile(br"[%\S] ?Error: ?[\s]+", re.I),
re.compile(br"[%\S] ?Informational: ?[\s]+", re.I),
re.compile(br"Command authorization failed"),
]
def on_open_shell(self):
try:
self._exec_cli_command(b"terminal datadump")
except AnsibleConnectionFailure as e:
raise_from(AnsibleConnectionFailure("unable to set terminal parameters"), e)
try:
self._exec_cli_command(b"terminal width 0")
except AnsibleConnectionFailure:
display.display(
"WARNING: Unable to set terminal width, command responses may be truncated"
)
try:
self._exec_cli_command(b"terminal no prompt")
except AnsibleConnectionFailure:
display.display(
"WARNING: Unable disable prompt, command responses may fail"
)
def on_become(self, passwd=None):
if self._get_prompt().endswith(b"#"):
return
cmd = {u"command": u"enable"}
if passwd:
# Note: python-3.5 cannot combine u"" and r"" together. Thus make
# an r string and use to_text to ensure it's text on both py2 and py3.
cmd[u"prompt"] = to_text(
r"[\r\n]?(?:.*)?[Pp]assword: ?$", errors="surrogate_or_strict"
)
cmd[u"answer"] = passwd
cmd[u"prompt_retry_check"] = True
try:
self._exec_cli_command(
to_bytes(json.dumps(cmd), errors="surrogate_or_strict")
)
prompt = self._get_prompt()
if prompt is None or not prompt.endswith(b"#"):
raise AnsibleConnectionFailure(
"failed to elevate privilege to enable mode still at prompt [%s]"
% prompt
)
except AnsibleConnectionFailure as e:
prompt = self._get_prompt()
raise_from(AnsibleConnectionFailure(
"unable to elevate privilege to enable mode, at prompt [%s] with error: %s"
% (prompt, e.message)
), e)
def on_unbecome(self):
prompt = self._get_prompt()
if prompt is None:
# if prompt is None most likely the terminal is hung up at a prompt
return
if b"(config" in prompt:
self._exec_cli_command(b"end")
self._exec_cli_command(b"disable")
elif prompt.endswith(b"#"):
self._exec_cli_command(b"disable")