summaryrefslogtreecommitdiffstats
path: root/ansible_collections/community/ciscosmb/plugins
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-13 12:04:41 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-13 12:04:41 +0000
commit975f66f2eebe9dadba04f275774d4ab83f74cf25 (patch)
tree89bd26a93aaae6a25749145b7e4bca4a1e75b2be /ansible_collections/community/ciscosmb/plugins
parentInitial commit. (diff)
downloadansible-975f66f2eebe9dadba04f275774d4ab83f74cf25.tar.xz
ansible-975f66f2eebe9dadba04f275774d4ab83f74cf25.zip
Adding upstream version 7.7.0+dfsg.upstream/7.7.0+dfsg
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'ansible_collections/community/ciscosmb/plugins')
-rw-r--r--ansible_collections/community/ciscosmb/plugins/cliconf/ciscosmb.py77
-rw-r--r--ansible_collections/community/ciscosmb/plugins/module_utils/__init__.py0
-rw-r--r--ansible_collections/community/ciscosmb/plugins/module_utils/ciscosmb.py302
-rw-r--r--ansible_collections/community/ciscosmb/plugins/module_utils/ciscosmb_canonical_map.py165
-rw-r--r--ansible_collections/community/ciscosmb/plugins/modules/__init__.py0
-rw-r--r--ansible_collections/community/ciscosmb/plugins/modules/command.py185
-rw-r--r--ansible_collections/community/ciscosmb/plugins/modules/facts.py763
-rw-r--r--ansible_collections/community/ciscosmb/plugins/terminal/ciscosmb.py123
8 files changed, 1615 insertions, 0 deletions
diff --git a/ansible_collections/community/ciscosmb/plugins/cliconf/ciscosmb.py b/ansible_collections/community/ciscosmb/plugins/cliconf/ciscosmb.py
new file mode 100644
index 000000000..922edeb69
--- /dev/null
+++ b/ansible_collections/community/ciscosmb/plugins/cliconf/ciscosmb.py
@@ -0,0 +1,77 @@
+#
+# (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 json
+
+from ansible.plugins.cliconf import CliconfBase, enable_mode
+
+
+class Cliconf(CliconfBase):
+
+ def get_device_info(self):
+ device_info = {}
+ device_info['network_os'] = 'ciscosmb'
+
+ 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)
diff --git a/ansible_collections/community/ciscosmb/plugins/module_utils/__init__.py b/ansible_collections/community/ciscosmb/plugins/module_utils/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/ansible_collections/community/ciscosmb/plugins/module_utils/__init__.py
diff --git a/ansible_collections/community/ciscosmb/plugins/module_utils/ciscosmb.py b/ansible_collections/community/ciscosmb/plugins/module_utils/ciscosmb.py
new file mode 100644
index 000000000..db19edec6
--- /dev/null
+++ b/ansible_collections/community/ciscosmb/plugins/module_utils/ciscosmb.py
@@ -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
diff --git a/ansible_collections/community/ciscosmb/plugins/module_utils/ciscosmb_canonical_map.py b/ansible_collections/community/ciscosmb/plugins/module_utils/ciscosmb_canonical_map.py
new file mode 100644
index 000000000..1ca8a665f
--- /dev/null
+++ b/ansible_collections/community/ciscosmb/plugins/module_utils/ciscosmb_canonical_map.py
@@ -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",
+}
diff --git a/ansible_collections/community/ciscosmb/plugins/modules/__init__.py b/ansible_collections/community/ciscosmb/plugins/modules/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/ansible_collections/community/ciscosmb/plugins/modules/__init__.py
diff --git a/ansible_collections/community/ciscosmb/plugins/modules/command.py b/ansible_collections/community/ciscosmb/plugins/modules/command.py
new file mode 100644
index 000000000..fa0290279
--- /dev/null
+++ b/ansible_collections/community/ciscosmb/plugins/modules/command.py
@@ -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()
diff --git a/ansible_collections/community/ciscosmb/plugins/modules/facts.py b/ansible_collections/community/ciscosmb/plugins/modules/facts.py
new file mode 100644
index 000000000..23cd9820a
--- /dev/null
+++ b/ansible_collections/community/ciscosmb/plugins/modules/facts.py
@@ -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()
diff --git a/ansible_collections/community/ciscosmb/plugins/terminal/ciscosmb.py b/ansible_collections/community/ciscosmb/plugins/terminal/ciscosmb.py
new file mode 100644
index 000000000..e8aa46d9b
--- /dev/null
+++ b/ansible_collections/community/ciscosmb/plugins/terminal/ciscosmb.py
@@ -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")