summaryrefslogtreecommitdiffstats
path: root/collections-debian-merged/ansible_collections/community/routeros/plugins
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-14 20:03:01 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-14 20:03:01 +0000
commita453ac31f3428614cceb99027f8efbdb9258a40b (patch)
treef61f87408f32a8511cbd91799f9cececb53e0374 /collections-debian-merged/ansible_collections/community/routeros/plugins
parentInitial commit. (diff)
downloadansible-upstream.tar.xz
ansible-upstream.zip
Adding upstream version 2.10.7+merged+base+2.10.8+dfsg.upstream/2.10.7+merged+base+2.10.8+dfsgupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'collections-debian-merged/ansible_collections/community/routeros/plugins')
-rw-r--r--collections-debian-merged/ansible_collections/community/routeros/plugins/cliconf/routeros.py79
-rw-r--r--collections-debian-merged/ansible_collections/community/routeros/plugins/module_utils/__init__.py0
-rw-r--r--collections-debian-merged/ansible_collections/community/routeros/plugins/module_utils/routeros.py163
-rw-r--r--collections-debian-merged/ansible_collections/community/routeros/plugins/modules/api.py480
-rw-r--r--collections-debian-merged/ansible_collections/community/routeros/plugins/modules/command.py180
-rw-r--r--collections-debian-merged/ansible_collections/community/routeros/plugins/modules/facts.py625
-rw-r--r--collections-debian-merged/ansible_collections/community/routeros/plugins/terminal/routeros.py69
7 files changed, 1596 insertions, 0 deletions
diff --git a/collections-debian-merged/ansible_collections/community/routeros/plugins/cliconf/routeros.py b/collections-debian-merged/ansible_collections/community/routeros/plugins/cliconf/routeros.py
new file mode 100644
index 00000000..2e4adf7c
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/community/routeros/plugins/cliconf/routeros.py
@@ -0,0 +1,79 @@
+#
+# (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: routeros
+short_description: Use routeros cliconf to run command on MikroTik RouterOS platform
+description:
+ - This routeros plugin provides low level abstraction apis for
+ sending and receiving CLI commands from MikroTik RouterOS network devices.
+'''
+
+import re
+import json
+
+from itertools import chain
+
+from ansible.module_utils._text import to_bytes, to_text
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import to_list
+from ansible.plugins.cliconf import CliconfBase, enable_mode
+
+
+class Cliconf(CliconfBase):
+
+ def get_device_info(self):
+ device_info = {}
+ device_info['network_os'] = 'RouterOS'
+
+ resource = self.get('/system resource print')
+ data = to_text(resource, errors='surrogate_or_strict').strip()
+ match = re.search(r'version: (\S+)', data)
+ if match:
+ device_info['network_os_version'] = match.group(1)
+
+ routerboard = self.get('/system routerboard print')
+ data = to_text(routerboard, errors='surrogate_or_strict').strip()
+ match = re.search(r'model: (.+)$', data, re.M)
+ if match:
+ device_info['network_os_model'] = match.group(1)
+
+ identity = self.get('/system identity print')
+ data = to_text(identity, errors='surrogate_or_strict').strip()
+ match = re.search(r'name: (.+)$', data, re.M)
+ if match:
+ device_info['network_os_hostname'] = match.group(1)
+
+ return device_info
+
+ def get_config(self, source='running', format='text', flags=None):
+ return
+
+ 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(Cliconf, self).get_capabilities()
+ return json.dumps(result)
diff --git a/collections-debian-merged/ansible_collections/community/routeros/plugins/module_utils/__init__.py b/collections-debian-merged/ansible_collections/community/routeros/plugins/module_utils/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/community/routeros/plugins/module_utils/__init__.py
diff --git a/collections-debian-merged/ansible_collections/community/routeros/plugins/module_utils/routeros.py b/collections-debian-merged/ansible_collections/community/routeros/plugins/module_utils/routeros.py
new file mode 100644
index 00000000..20a8400b
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/community/routeros/plugins/module_utils/routeros.py
@@ -0,0 +1,163 @@
+# 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
+from ansible.module_utils._text import 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
+
+_DEVICE_CONFIGS = {}
+
+routeros_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')
+}
+routeros_argument_spec = {}
+
+
+def get_provider_argspec():
+ return routeros_provider_spec
+
+
+def get_connection(module):
+ if hasattr(module, '_routeros_connection'):
+ return module._routeros_connection
+
+ capabilities = get_capabilities(module)
+ network_api = capabilities.get('network_api')
+ if network_api == 'cliconf':
+ module._routeros_connection = Connection(module._socket_path)
+ else:
+ module.fail_json(msg='Invalid connection type %s' % network_api)
+
+ return module._routeros_connection
+
+
+def get_capabilities(module):
+ if hasattr(module, '_routeros_capabilities'):
+ return module._routeros_capabilities
+
+ try:
+ capabilities = Connection(module._socket_path).get_capabilities()
+ module._routeros_capabilities = json.loads(capabilities)
+ return module._routeros_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_native(exc, errors='surrogate_then_replace'))
+
+ out = to_native(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_native(exc, errors='surrogate_then_replace'))
+
+ cfg = to_native(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_native(exc, errors='surrogate_then_replace'))
+
+ try:
+ out = to_native(out, errors='surrogate_or_strict')
+ except UnicodeError:
+ module.fail_json(
+ msg=u'Failed to decode output from %s: %s' % (cmd, to_native(out)))
+
+ responses.append(out)
+
+ return responses
+
+
+def load_config(module, commands):
+ connection = get_connection(module)
+
+ out = connection.edit_config(commands)
diff --git a/collections-debian-merged/ansible_collections/community/routeros/plugins/modules/api.py b/collections-debian-merged/ansible_collections/community/routeros/plugins/modules/api.py
new file mode 100644
index 00000000..bb5d7a86
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/community/routeros/plugins/modules/api.py
@@ -0,0 +1,480 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2020, Nikolay Dachev <nikolay@dachev.info>
+# GNU General Public License v3.0+ https://www.gnu.org/licenses/gpl-3.0.txt
+
+from __future__ import (absolute_import, division, print_function)
+__metaclass__ = type
+
+DOCUMENTATION = '''
+---
+module: api
+author: "Nikolay Dachev (@NikolayDachev)"
+short_description: Ansible module for RouterOS API
+description:
+ - Ansible module for RouterOS API with python librouteros.
+ - This module can add, remove, update, query and execute arbitrary command in routeros via API.
+notes:
+ - I(add), I(remove), I(update), I(cmd) and I(query) are mutually exclusive.
+ - I(check_mode) is not supported.
+requirements:
+ - librouteros
+ - Python >= 3.6 (for librouteros)
+options:
+ hostname:
+ description:
+ - RouterOS hostname API.
+ required: true
+ type: str
+ username:
+ description:
+ - RouterOS login user.
+ required: true
+ type: str
+ password:
+ description:
+ - RouterOS user password.
+ required: true
+ type: str
+ ssl:
+ description:
+ - If is set TLS will be used for RouterOS API connection.
+ required: false
+ type: bool
+ default: false
+ port:
+ description:
+ - RouterOS api port. If ssl is set, port will apply to ssl connection.
+ - Defaults are C(8728) for the HTTP API, and C(8729) for the HTTPS API.
+ type: int
+ path:
+ description:
+ - Main path for all other arguments.
+ - If other arguments are not set, api will return all items in selected path.
+ - Example C(ip address). Equivalent of RouterOS CLI C(/ip address print).
+ required: true
+ type: str
+ add:
+ description:
+ - Will add selected arguments in selected path to RouterOS config.
+ - Example C(address=1.1.1.1/32 interface=ether1).
+ - Equivalent in RouterOS CLI C(/ip address add address=1.1.1.1/32 interface=ether1).
+ type: str
+ remove:
+ description:
+ - Remove config/value from RouterOS by '.id'.
+ - Example C(*03) will remove config/value with C(id=*03) in selected path.
+ - Equivalent in RouterOS CLI C(/ip address remove numbers=1).
+ - Note C(number) in RouterOS CLI is different from C(.id).
+ type: str
+ update:
+ description:
+ - Update config/value in RouterOS by '.id' in selected path.
+ - Example C(.id=*03 address=1.1.1.3/32) and path C(ip address) will replace existing ip address with C(.id=*03).
+ - Equivalent in RouterOS CLI C(/ip address set address=1.1.1.3/32 numbers=1).
+ - Note C(number) in RouterOS CLI is different from C(.id).
+ type: str
+ query:
+ description:
+ - Query given path for selected query attributes from RouterOS aip and return '.id'.
+ - WHERE is key word which extend query. WHERE format is key operator value - with spaces.
+ - WHERE valid operators are C(==), C(!=), C(>), C(<).
+ - Example path C(ip address) and query C(.id address) will return only C(.id) and C(address) for all items in C(ip address) path.
+ - Example path C(ip address) and query C(.id address WHERE address == 1.1.1.3/32).
+ will return only C(.id) and C(address) for items in C(ip address) path, where address is eq to 1.1.1.3/32.
+ - Example path C(interface) and query C(mtu name WHERE mut > 1400) will
+ return only interfaces C(mtu,name) where mtu is bigger than 1400.
+ - Equivalent in RouterOS CLI C(/interface print where mtu > 1400).
+ type: str
+ cmd:
+ description:
+ - Execute any/arbitrary command in selected path, after the command we can add C(.id).
+ - Example path C(system script) and cmd C(run .id=*03) is equivalent in RouterOS CLI C(/system script run number=0).
+ - Example path C(ip address) and cmd C(print) is equivalent in RouterOS CLI C(/ip address print).
+ type: str
+'''
+
+EXAMPLES = '''
+---
+- name: Use RouterOS API
+ hosts: localhost
+ gather_facts: no
+ vars:
+ hostname: "ros_api_hostname/ip"
+ username: "admin"
+ password: "secret_password"
+
+ path: "ip address"
+
+ nic: "ether2"
+ ip1: "1.1.1.1/32"
+ ip2: "2.2.2.2/32"
+ ip3: "3.3.3.3/32"
+
+ tasks:
+ - name: Get "{{ path }} print"
+ community.routeros.api:
+ hostname: "{{ hostname }}"
+ password: "{{ password }}"
+ username: "{{ username }}"
+ path: "{{ path }}"
+ register: print_path
+
+ - name: Dump "{{ path }} print" output
+ ansible.builtin.debug:
+ msg: '{{ print_path }}'
+
+ - name: Add ip address "{{ ip1 }}" and "{{ ip2 }}"
+ community.routeros.api:
+ hostname: "{{ hostname }}"
+ password: "{{ password }}"
+ username: "{{ username }}"
+ path: "{{ path }}"
+ add: "{{ item }}"
+ loop:
+ - "address={{ ip1 }} interface={{ nic }}"
+ - "address={{ ip2 }} interface={{ nic }}"
+ register: addout
+
+ - name: Dump "Add ip address" output - ".id" for new added items
+ ansible.builtin.debug:
+ msg: '{{ addout }}'
+
+ - name: Query for ".id" in "{{ path }} WHERE address == {{ ip2 }}"
+ community.routeros.api:
+ hostname: "{{ hostname }}"
+ password: "{{ password }}"
+ username: "{{ username }}"
+ path: "{{ path }}"
+ query: ".id address WHERE address == {{ ip2 }}"
+ register: queryout
+
+ - name: Dump "Query for" output and set fact with ".id" for "{{ ip2 }}"
+ ansible.builtin.debug:
+ msg: '{{ queryout }}'
+
+ - name: Store query_id for later usage
+ ansible.builtin.set_fact:
+ query_id: "{{ queryout['msg'][0]['.id'] }}"
+
+ - name: Update ".id = {{ query_id }}" taken with custom fact "fquery_id"
+ community.routeros.api:
+ hostname: "{{ hostname }}"
+ password: "{{ password }}"
+ username: "{{ username }}"
+ path: "{{ path }}"
+ update: ".id={{ query_id }} address={{ ip3 }}"
+ register: updateout
+
+ - name: Dump "Update" output
+ ansible.builtin.debug:
+ msg: '{{ updateout }}'
+
+ - name: Remove ips - stage 1 - query ".id" for "{{ ip2 }}" and "{{ ip3 }}"
+ community.routeros.api:
+ hostname: "{{ hostname }}"
+ password: "{{ password }}"
+ username: "{{ username }}"
+ path: "{{ path }}"
+ query: ".id address WHERE address == {{ item }}"
+ register: id_to_remove
+ loop:
+ - "{{ ip2 }}"
+ - "{{ ip3 }}"
+
+ - name: Set fact for ".id" from "Remove ips - stage 1 - query"
+ ansible.builtin.set_fact:
+ to_be_remove: "{{ to_be_remove |default([]) + [item['msg'][0]['.id']] }}"
+ loop: "{{ id_to_remove.results }}"
+
+ - name: Dump "Remove ips - stage 1 - query" output
+ ansible.builtin.debug:
+ msg: '{{ to_be_remove }}'
+
+ # Remove "{{ rmips }}" with ".id" by "to_be_remove" from query
+ - name: Remove ips - stage 2 - remove "{{ ip2 }}" and "{{ ip3 }}" by '.id'
+ community.routeros.api:
+ hostname: "{{ hostname }}"
+ password: "{{ password }}"
+ username: "{{ username }}"
+ path: "{{ path }}"
+ remove: "{{ item }}"
+ register: remove
+ loop: "{{ to_be_remove }}"
+
+ - name: Dump "Remove ips - stage 2 - remove" output
+ ansible.builtin.debug:
+ msg: '{{ remove }}'
+
+ - name: Arbitrary command example "/system identity print"
+ community.routeros.api:
+ hostname: "{{ hostname }}"
+ password: "{{ password }}"
+ username: "{{ username }}"
+ path: "system identity"
+ cmd: "print"
+ register: cmdout
+
+ - name: Dump "Arbitrary command example" output
+ ansible.builtin.debug:
+ msg: "{{ cmdout }}"
+'''
+
+RETURN = '''
+---
+message:
+ description: All outputs are in list with dictionary elements returned from RouterOS api.
+ sample: C([{...},{...}])
+ type: list
+ returned: always
+'''
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible.module_utils.basic import missing_required_lib
+from ansible.module_utils._text import to_native
+
+import ssl
+import traceback
+
+LIB_IMP_ERR = None
+try:
+ from librouteros import connect
+ from librouteros.query import Key
+ HAS_LIB = True
+except Exception as e:
+ HAS_LIB = False
+ LIB_IMP_ERR = traceback.format_exc()
+
+
+class ROS_api_module:
+ def __init__(self):
+ module_args = (dict(
+ username=dict(type='str', required=True),
+ password=dict(type='str', required=True, no_log=True),
+ hostname=dict(type='str', required=True),
+ port=dict(type='int'),
+ ssl=dict(type='bool', default=False),
+ path=dict(type='str', required=True),
+ add=dict(type='str'),
+ remove=dict(type='str'),
+ update=dict(type='str'),
+ cmd=dict(type='str'),
+ query=dict(type='str')))
+
+ self.module = AnsibleModule(argument_spec=module_args,
+ supports_check_mode=False,
+ mutually_exclusive=(('add', 'remove', 'update',
+ 'cmd', 'query'),),)
+
+ if not HAS_LIB:
+ self.module.fail_json(msg=missing_required_lib("librouteros"),
+ exception=LIB_IMP_ERR)
+
+ self.api = self.ros_api_connect(self.module.params['username'],
+ self.module.params['password'],
+ self.module.params['hostname'],
+ self.module.params['port'],
+ self.module.params['ssl'])
+
+ self.path = self.list_remove_empty(self.module.params['path'].split(' '))
+ self.add = self.module.params['add']
+ self.remove = self.module.params['remove']
+ self.update = self.module.params['update']
+ self.arbitrary = self.module.params['cmd']
+
+ self.where = None
+ self.query = self.module.params['query']
+ if self.query:
+ if 'WHERE' in self.query:
+ split = self.query.split('WHERE')
+ self.query = self.list_remove_empty(split[0].split(' '))
+ self.where = self.list_remove_empty(split[1].split(' '))
+ else:
+ self.query = self.list_remove_empty(self.module.params['query'].split(' '))
+
+ self.result = dict(
+ message=[])
+
+ # create api base path
+ self.api_path = self.api_add_path(self.api, self.path)
+
+ # api call's
+ if self.add:
+ self.api_add()
+ elif self.remove:
+ self.api_remove()
+ elif self.update:
+ self.api_update()
+ elif self.query:
+ self.api_query()
+ elif self.arbitrary:
+ self.api_arbitrary()
+ else:
+ self.api_get_all()
+
+ def list_remove_empty(self, check_list):
+ while("" in check_list):
+ check_list.remove("")
+ return check_list
+
+ def list_to_dic(self, ldict):
+ dict = {}
+ for p in ldict:
+ if '=' not in p:
+ self.errors("missing '=' after '%s'" % p)
+ p = p.split('=')
+ if p[1]:
+ dict[p[0]] = p[1]
+ return dict
+
+ def api_add_path(self, api, path):
+ api_path = api.path()
+ for p in path:
+ api_path = api_path.join(p)
+ return api_path
+
+ def api_get_all(self):
+ try:
+ for i in self.api_path:
+ self.result['message'].append(i)
+ self.return_result(False, True)
+ except Exception as e:
+ self.errors(e)
+
+ def api_add(self):
+ param = self.list_to_dic(self.add.split(' '))
+ try:
+ self.result['message'].append("added: .id= %s"
+ % self.api_path.add(**param))
+ self.return_result(True)
+ except Exception as e:
+ self.errors(e)
+
+ def api_remove(self):
+ try:
+ self.api_path.remove(self.remove)
+ self.result['message'].append("removed: .id= %s" % self.remove)
+ self.return_result(True)
+ except Exception as e:
+ self.errors(e)
+
+ def api_update(self):
+ param = self.list_to_dic(self.update.split(' '))
+ if '.id' not in param.keys():
+ self.errors("missing '.id' for %s" % param)
+ try:
+ self.api_path.update(**param)
+ self.result['message'].append("updated: %s" % param)
+ self.return_result(True)
+ except Exception as e:
+ self.errors(e)
+
+ def api_query(self):
+ keys = {}
+ for k in self.query:
+ if 'id' in k and k != ".id":
+ self.errors("'%s' must be '.id'" % k)
+ keys[k] = Key(k)
+ try:
+ if self.where:
+ if len(self.where) < 3:
+ self.errors("invalid syntax for 'WHERE %s'"
+ % ' '.join(self.where))
+
+ where = []
+ if self.where[1] == '==':
+ select = self.api_path.select(*keys).where(keys[self.where[0]] == self.where[2])
+ elif self.where[1] == '!=':
+ select = self.api_path.select(*keys).where(keys[self.where[0]] != self.where[2])
+ elif self.where[1] == '>':
+ select = self.api_path.select(*keys).where(keys[self.where[0]] > self.where[2])
+ elif self.where[1] == '<':
+ select = self.api_path.select(*keys).where(keys[self.where[0]] < self.where[2])
+ else:
+ self.errors("'%s' is not operator for 'where'"
+ % self.where[1])
+ for row in select:
+ self.result['message'].append(row)
+ else:
+ for row in self.api_path.select(*keys):
+ self.result['message'].append(row)
+ if len(self.result['message']) < 1:
+ msg = "no results for '%s 'query' %s" % (' '.join(self.path),
+ ' '.join(self.query))
+ if self.where:
+ msg = msg + ' WHERE %s' % ' '.join(self.where)
+ self.result['message'].append(msg)
+ self.return_result(False)
+ except Exception as e:
+ self.errors(e)
+
+ def api_arbitrary(self):
+ param = {}
+ self.arbitrary = self.arbitrary.split(' ')
+ arb_cmd = self.arbitrary[0]
+ if len(self.arbitrary) > 1:
+ param = self.list_to_dic(self.arbitrary[1:])
+ try:
+ arbitrary_result = self.api_path(arb_cmd, **param)
+ for i in arbitrary_result:
+ self.result['message'].append(i)
+ self.return_result(False)
+ except Exception as e:
+ self.errors(e)
+
+ def return_result(self, ch_status=False, status=True):
+ if status == "False":
+ self.module.fail_json(msg=to_native(self.result['message']))
+ else:
+ self.module.exit_json(changed=ch_status,
+ msg=self.result['message'])
+
+ def errors(self, e):
+ if e.__class__.__name__ == 'TrapError':
+ self.result['message'].append("%s" % e)
+ self.return_result(False, True)
+ self.result['message'].append("%s" % e)
+ self.return_result(False, False)
+
+ def ros_api_connect(self, username, password, host, port, use_ssl):
+ # connect to routeros api
+ conn_status = {"connection": {"username": username,
+ "hostname": host,
+ "port": port,
+ "ssl": use_ssl,
+ "status": "Connected"}}
+ try:
+ if use_ssl is True:
+ if not port:
+ port = 8729
+ conn_status["connection"]["port"] = port
+ ctx = ssl.create_default_context()
+ ctx.check_hostname = False
+ ctx.set_ciphers('ADH:@SECLEVEL=0')
+ api = connect(username=username,
+ password=password,
+ host=host,
+ ssl_wrapper=ctx.wrap_socket,
+ port=port)
+ else:
+ if not port:
+ port = 8728
+ conn_status["connection"]["port"] = port
+ api = connect(username=username,
+ password=password,
+ host=host,
+ port=port)
+ except Exception as e:
+ conn_status["connection"]["status"] = "error: %s" % e
+ self.module.fail_json(msg=to_native([conn_status]))
+ return api
+
+
+def main():
+
+ ROS_api_module()
+
+
+if __name__ == '__main__':
+ main()
diff --git a/collections-debian-merged/ansible_collections/community/routeros/plugins/modules/command.py b/collections-debian-merged/ansible_collections/community/routeros/plugins/modules/command.py
new file mode 100644
index 00000000..82331a57
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/community/routeros/plugins/modules/command.py
@@ -0,0 +1,180 @@
+#!/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: "Egor Zaitsev (@heuels)"
+short_description: Run commands on remote devices running MikroTik RouterOS
+description:
+ - Sends arbitrary commands to an RouterOS 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 RouterOS 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
+ 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: Run command on remote devices
+ community.routeros.command:
+ commands: /system routerboard print
+
+- name: Run command and check to see if output contains routeros
+ community.routeros.command:
+ commands: /system resource print
+ wait_for: result[0] contains MikroTik
+
+- name: Run multiple commands on remote nodes
+ community.routeros.command:
+ commands:
+ - /system routerboard print
+ - /system identity print
+
+- name: Run multiple commands and evaluate the output
+ community.routeros.command:
+ commands:
+ - /system routerboard print
+ - /interface ethernet print
+ wait_for:
+ - result[0] contains x86
+ - result[1] contains ether1
+"""
+
+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 re
+import time
+
+from ansible_collections.community.routeros.plugins.module_utils.routeros import run_commands
+from ansible_collections.community.routeros.plugins.module_utils.routeros import routeros_argument_spec
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import ComplexList
+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():
+ """main entry point for module execution
+ """
+ argument_spec = dict(
+ 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')
+ )
+
+ argument_spec.update(routeros_argument_spec)
+
+ module = AnsibleModule(argument_spec=argument_spec,
+ supports_check_mode=True)
+
+ 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/collections-debian-merged/ansible_collections/community/routeros/plugins/modules/facts.py b/collections-debian-merged/ansible_collections/community/routeros/plugins/modules/facts.py
new file mode 100644
index 00000000..4a4db7c3
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/community/routeros/plugins/modules/facts.py
@@ -0,0 +1,625 @@
+#!/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: "Egor Zaitsev (@heuels)"
+short_description: Collect facts from remote devices running MikroTik RouterOS
+description:
+ - Collects a base set of device facts from a remote device that
+ is running RotuerOS. 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
+ default: '!config'
+'''
+
+EXAMPLES = """
+- name: Collect all facts from the device
+ community.routeros.facts:
+ gather_subset: all
+
+- name: Collect only the config and default facts
+ community.routeros.facts:
+ gather_subset:
+ - config
+
+- name: Do not collect hardware facts
+ community.routeros.facts:
+ gather_subset:
+ - "!hardware"
+"""
+
+RETURN = """
+ansible_facts:
+ description: "Dictionary of ip geolocation facts for a host's IP address"
+ returned: always
+ type: dict
+ contains:
+ 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_arch:
+ description: The CPU architecture 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
+
+ # 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
+
+ # routing
+ ansible_net_bgp_peer:
+ description: The dict bgp peer
+ returned: peer information
+ type: dict
+ ansible_net_bgp_vpnv4_route:
+ description: The dict bgp vpnv4 route
+ returned: vpnv4 route information
+ type: dict
+ ansible_net_bgp_instance:
+ description: The dict bgp instance
+ returned: bgp instance information
+ type: dict
+ ansible_net_route:
+ description: The dict routes in all routing table
+ returned: routes information in all routing table
+ type: dict
+ ansible_net_ospf_instance:
+ description: The dict ospf instance
+ returned: ospf instance information
+ type: dict
+ ansible_net_ospf_neighbor:
+ description: The dict ospf neighbor
+ returned: ospf neighbor information
+ type: dict
+"""
+import re
+
+from ansible_collections.community.routeros.plugins.module_utils.routeros import run_commands
+from ansible_collections.community.routeros.plugins.module_utils.routeros import routeros_argument_spec
+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 = [
+ '/system identity print without-paging',
+ '/system resource print without-paging',
+ '/system routerboard print without-paging'
+ ]
+
+ def populate(self):
+ super(Default, self).populate()
+ data = self.responses[0]
+ if data:
+ self.facts['hostname'] = self.parse_hostname(data)
+ data = self.responses[1]
+ if data:
+ self.facts['version'] = self.parse_version(data)
+ self.facts['arch'] = self.parse_arch(data)
+ self.facts['uptime'] = self.parse_uptime(data)
+ self.facts['cpu_load'] = self.parse_cpu_load(data)
+ data = self.responses[2]
+ if data:
+ self.facts['model'] = self.parse_model(data)
+ self.facts['serialnum'] = self.parse_serialnum(data)
+
+ def parse_hostname(self, data):
+ match = re.search(r'name:\s(.*)\s*$', data, re.M)
+ if match:
+ return match.group(1)
+
+ def parse_version(self, data):
+ match = re.search(r'version:\s(.*)\s*$', data, re.M)
+ if match:
+ return match.group(1)
+
+ def parse_model(self, data):
+ match = re.search(r'model:\s(.*)\s*$', data, re.M)
+ if match:
+ return match.group(1)
+
+ def parse_arch(self, data):
+ match = re.search(r'architecture-name:\s(.*)\s*$', data, re.M)
+ if match:
+ return match.group(1)
+
+ def parse_uptime(self, data):
+ match = re.search(r'uptime:\s(.*)\s*$', data, re.M)
+ if match:
+ return match.group(1)
+
+ def parse_cpu_load(self, data):
+ match = re.search(r'cpu-load:\s(.*)\s*$', data, re.M)
+ if match:
+ return match.group(1)
+
+ def parse_serialnum(self, data):
+ match = re.search(r'serial-number:\s(.*)\s*$', data, re.M)
+ if match:
+ return match.group(1)
+
+
+class Hardware(FactsBase):
+
+ COMMANDS = [
+ '/system resource print without-paging'
+ ]
+
+ def populate(self):
+ super(Hardware, self).populate()
+ data = self.responses[0]
+ if data:
+ self.parse_filesystem_info(data)
+ self.parse_memory_info(data)
+
+ def parse_filesystem_info(self, data):
+ match = re.search(r'free-hdd-space:\s(.*)([KMG]iB)', data, re.M)
+ if match:
+ self.facts['spacefree_mb'] = self.to_megabytes(match)
+ match = re.search(r'total-hdd-space:\s(.*)([KMG]iB)', data, re.M)
+ if match:
+ self.facts['spacetotal_mb'] = self.to_megabytes(match)
+
+ def parse_memory_info(self, data):
+ match = re.search(r'free-memory:\s(\d+\.?\d*)([KMG]iB)', data, re.M)
+ if match:
+ self.facts['memfree_mb'] = self.to_megabytes(match)
+ match = re.search(r'total-memory:\s(\d+\.?\d*)([KMG]iB)', data, re.M)
+ if match:
+ self.facts['memtotal_mb'] = self.to_megabytes(match)
+
+ def to_megabytes(self, data):
+ if data.group(2) == 'KiB':
+ return float(data.group(1)) / 1024
+ elif data.group(2) == 'MiB':
+ return float(data.group(1))
+ elif data.group(2) == 'GiB':
+ return float(data.group(1)) * 1024
+ else:
+ return None
+
+
+class Config(FactsBase):
+
+ COMMANDS = ['/export verbose']
+
+ def populate(self):
+ super(Config, self).populate()
+ data = self.responses[0]
+ if data:
+ self.facts['config'] = data
+
+
+class Interfaces(FactsBase):
+
+ COMMANDS = [
+ '/interface print detail without-paging',
+ '/ip address print detail without-paging',
+ '/ipv6 address print detail without-paging',
+ '/ip neighbor print detail without-paging'
+ ]
+
+ 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:
+ interfaces = self.parse_interfaces(data)
+ self.populate_interfaces(interfaces)
+
+ data = self.responses[1]
+ if data:
+ data = self.parse_detail(data)
+ self.populate_addresses(data, 'ipv4')
+
+ data = self.responses[2]
+ if data:
+ data = self.parse_detail(data)
+ self.populate_addresses(data, 'ipv6')
+
+ data = self.responses[3]
+ if data:
+ self.facts['neighbors'] = list(self.parse_detail(data))
+
+ def populate_interfaces(self, data):
+ for key, value in iteritems(data):
+ self.facts['interfaces'][key] = value
+
+ def populate_addresses(self, data, family):
+ for value in data:
+ key = value['interface']
+ if family not in self.facts['interfaces'][key]:
+ self.facts['interfaces'][key][family] = list()
+ addr, subnet = value['address'].split("/")
+ ip = dict(address=addr.strip(), subnet=subnet.strip())
+ self.add_ip_address(addr.strip(), family)
+ self.facts['interfaces'][key][family].append(ip)
+
+ def add_ip_address(self, address, family):
+ if family == 'ipv4':
+ self.facts['all_ipv4_addresses'].append(address)
+ else:
+ self.facts['all_ipv6_addresses'].append(address)
+
+ def preprocess(self, data):
+ preprocessed = list()
+ for line in data.split('\n'):
+ if len(line) == 0 or line[:5] == 'Flags':
+ continue
+ elif not re.match(self.WRAPPED_LINE_RE, line):
+ preprocessed.append(line)
+ else:
+ preprocessed[-1] += line
+ return preprocessed
+
+ def parse_interfaces(self, data):
+ facts = dict()
+ data = self.preprocess(data)
+ for line in data:
+ parsed = dict(re.findall(self.DETAIL_RE, line))
+ if "name" not in parsed:
+ continue
+ facts[parsed["name"]] = dict(re.findall(self.DETAIL_RE, line))
+ return facts
+
+ def parse_detail(self, data):
+ data = self.preprocess(data)
+ for line in data:
+ parsed = dict(re.findall(self.DETAIL_RE, line))
+ if "interface" not in parsed:
+ continue
+ yield parsed
+
+
+class Routing(FactsBase):
+
+ COMMANDS = [
+ '/routing bgp peer print detail without-paging',
+ '/routing bgp vpnv4-route print detail without-paging',
+ '/routing bgp instance print detail without-paging',
+ '/ip route print detail without-paging',
+ '/routing ospf instance print detail without-paging',
+ '/routing ospf neighbor print detail without-paging'
+ ]
+
+ 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(Routing, self).populate()
+ self.facts['bgp_peer'] = dict()
+ self.facts['bgp_vpnv4_route'] = dict()
+ self.facts['bgp_instance'] = dict()
+ self.facts['route'] = dict()
+ self.facts['ospf_instance'] = dict()
+ self.facts['ospf_neighbor'] = dict()
+ data = self.responses[0]
+ if data:
+ peer = self.parse_bgp_peer(data)
+ self.populate_bgp_peer(peer)
+ data = self.responses[1]
+ if data:
+ vpnv4 = self.parse_vpnv4_route(data)
+ self.populate_vpnv4_route(vpnv4)
+ data = self.responses[2]
+ if data:
+ instance = self.parse_instance(data)
+ self.populate_bgp_instance(instance)
+ data = self.responses[3]
+ if data:
+ route = self.parse_route(data)
+ self.populate_route(route)
+ data = self.responses[4]
+ if data:
+ instance = self.parse_instance(data)
+ self.populate_ospf_instance(instance)
+ data = self.responses[5]
+ if data:
+ instance = self.parse_ospf_neighbor(data)
+ self.populate_ospf_neighbor(instance)
+
+ def preprocess(self, data):
+ preprocessed = list()
+ for line in data.split('\n'):
+ if len(line) == 0 or line[:5] == 'Flags':
+ continue
+ elif not re.match(self.WRAPPED_LINE_RE, line):
+ preprocessed.append(line)
+ else:
+ preprocessed[-1] += line
+ return preprocessed
+
+ def parse_name(self, data):
+ match = re.search(r'name=.(\S+\b)', data, re.M)
+ if match:
+ return match.group(1)
+
+ def parse_interface(self, data):
+ match = re.search(r'interface=([\w\d\-]+)', data, re.M)
+ if match:
+ return match.group(1)
+
+ def parse_instance_name(self, data):
+ match = re.search(r'instance=([\w\d\-]+)', data, re.M)
+ if match:
+ return match.group(1)
+
+ def parse_routing_mark(self, data):
+ match = re.search(r'routing-mark=([\w\d\-]+)', data, re.M)
+ if match:
+ return match.group(1)
+ else:
+ match = 'main'
+ return match
+
+ def parse_bgp_peer(self, data):
+ facts = dict()
+ data = self.preprocess(data)
+ for line in data:
+ name = self.parse_name(line)
+ facts[name] = dict()
+ for (key, value) in re.findall(self.DETAIL_RE, line):
+ facts[name][key] = value
+ return facts
+
+ def parse_instance(self, data):
+ facts = dict()
+ data = self.preprocess(data)
+ for line in data:
+ name = self.parse_name(line)
+ facts[name] = dict()
+ for (key, value) in re.findall(self.DETAIL_RE, line):
+ facts[name][key] = value
+ return facts
+
+ def parse_vpnv4_route(self, data):
+ facts = dict()
+ data = self.preprocess(data)
+ for line in data:
+ name = self.parse_interface(line)
+ facts[name] = dict()
+ for (key, value) in re.findall(self.DETAIL_RE, line):
+ facts[name][key] = value
+ return facts
+
+ def parse_route(self, data):
+ facts = dict()
+ data = self.preprocess(data)
+ for line in data:
+ name = self.parse_routing_mark(line)
+ facts[name] = dict()
+ for (key, value) in re.findall(self.DETAIL_RE, line):
+ facts[name][key] = value
+ return facts
+
+ def parse_ospf_instance(self, data):
+ facts = dict()
+ data = self.preprocess(data)
+ for line in data:
+ name = self.parse_name(line)
+ facts[name] = dict()
+ for (key, value) in re.findall(self.DETAIL_RE, line):
+ facts[name][key] = value
+ return facts
+
+ def parse_ospf_neighbor(self, data):
+ facts = dict()
+ data = self.preprocess(data)
+ for line in data:
+ name = self.parse_instance_name(line)
+ facts[name] = dict()
+ for (key, value) in re.findall(self.DETAIL_RE, line):
+ facts[name][key] = value
+ return facts
+
+ def populate_bgp_peer(self, data):
+ for key, value in iteritems(data):
+ self.facts['bgp_peer'][key] = value
+
+ def populate_vpnv4_route(self, data):
+ for key, value in iteritems(data):
+ self.facts['bgp_vpnv4_route'][key] = value
+
+ def populate_bgp_instance(self, data):
+ for key, value in iteritems(data):
+ self.facts['bgp_instance'][key] = value
+
+ def populate_route(self, data):
+ for key, value in iteritems(data):
+ self.facts['route'][key] = value
+
+ def populate_ospf_instance(self, data):
+ for key, value in iteritems(data):
+ self.facts['ospf_instance'][key] = value
+
+ def populate_ospf_neighbor(self, data):
+ for key, value in iteritems(data):
+ self.facts['ospf_neighbor'][key] = value
+
+
+FACT_SUBSETS = dict(
+ default=Default,
+ hardware=Hardware,
+ interfaces=Interfaces,
+ config=Config,
+ routing=Routing,
+)
+
+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')
+ )
+
+ argument_spec.update(routeros_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/collections-debian-merged/ansible_collections/community/routeros/plugins/terminal/routeros.py b/collections-debian-merged/ansible_collections/community/routeros/plugins/terminal/routeros.py
new file mode 100644
index 00000000..2c29d1db
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/community/routeros/plugins/terminal/routeros.py
@@ -0,0 +1,69 @@
+#
+# (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
+
+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):
+
+ ansi_re = [
+ # check ECMA-48 Section 5.4 (Control Sequences)
+ re.compile(br'(\x1b\[\?1h\x1b=)'),
+ re.compile(br'((?:\x9b|\x1b\x5b)[\x30-\x3f]*[\x20-\x2f]*[\x40-\x7e])'),
+ re.compile(br'\x08.')
+ ]
+
+ terminal_initial_prompt = [
+ br'\x1bZ',
+ ]
+
+ terminal_initial_answer = b'\x1b/Z'
+
+ terminal_stdout_re = [
+ re.compile(br"\x1b<"),
+ re.compile(br"\[[\w\-\.]+\@[\w\s\-\.\/]+\] ?> ?$"),
+ re.compile(br"Please press \"Enter\" to continue!"),
+ re.compile(br"Do you want to see the software license\? \[Y\/n\]: ?"),
+ ]
+
+ terminal_stderr_re = [
+ re.compile(br"\nbad command name"),
+ re.compile(br"\nno such item"),
+ re.compile(br"\ninvalid value for"),
+ ]
+
+ def on_open_shell(self):
+ prompt = self._get_prompt()
+ try:
+ if prompt.strip().endswith(b':'):
+ self._exec_cli_command(b' ')
+ if prompt.strip().endswith(b'!'):
+ self._exec_cli_command(b'\n')
+ except AnsibleConnectionFailure:
+ raise AnsibleConnectionFailure('unable to bypass license prompt')