Add api_facts module (#88)

* Add API docs fragment.

* Improve documentation.

* Move API code to api module_utils.

* Improve docs.

* Add api_facts module.

Does not yet support 'config'. I'm not sure whether that's actually
possible with the API.

* Convert subnet to integer if possible.

* Cleanup.

* Linting and fix tests.

* Remove things that make no sense.

* Simplify code.

* Add basic tests.

* Lint.
This commit is contained in:
Felix Fontein 2022-05-12 16:17:43 +02:00 committed by GitHub
parent a90c696589
commit 3d80ccec5f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 1618 additions and 277 deletions

View file

@ -13,43 +13,14 @@ 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.
- Ansible module for RouterOS API with the Python C(librouteros) library.
- 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)
extends_documentation_fragment:
- community.routeros.api
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
tls:
description:
- If is set TLS will be used for RouterOS API connection.
required: false
type: bool
default: false
aliases:
- ssl
port:
description:
- RouterOS api port. If I(tls) is set, port will apply to TLS/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.
@ -95,33 +66,7 @@ options:
- 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
validate_certs:
description:
- Set to C(false) to skip validation of TLS certificates.
- See also I(validate_cert_hostname). Only used when I(tls=true).
- B(Note:) instead of simply deactivating certificate validations to "make things work",
please consider creating your own CA certificate and using it to sign certificates used
for your router. You can tell the module about your CA certificate with the I(ca_path)
option.
type: bool
default: true
version_added: 1.2.0
validate_cert_hostname:
description:
- Set to C(true) to validate hostnames in certificates.
- See also I(validate_certs). Only used when I(tls=true) and I(validate_certs=true).
type: bool
default: false
version_added: 1.2.0
ca_path:
description:
- PEM formatted file that contains a CA certificate to be used for certificate validation.
- See also I(validate_cert_hostname). Only used when I(tls=true) and I(validate_certs=true).
type: path
version_added: 1.2.0
seealso:
- ref: ansible_collections.community.routeros.docsite.api-guide
description: How to connect to RouterOS devices with the RouterOS API
- ref: ansible_collections.community.routeros.docsite.quoting
description: How to quote and unquote commands and arguments
'''
@ -275,58 +220,44 @@ from ansible_collections.community.routeros.plugins.module_utils.quoting import
split_routeros_command,
)
from ansible_collections.community.routeros.plugins.module_utils.api import (
api_argument_spec,
check_has_library,
create_api,
)
import re
import ssl
import traceback
LIB_IMP_ERR = None
try:
from librouteros import connect
from librouteros.exceptions import LibRouterosError
from librouteros.query import Key
HAS_LIB = True
except Exception as e:
HAS_LIB = False
LIB_IMP_ERR = traceback.format_exc()
except Exception:
# Handled in api module_utils
pass
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'),
tls=dict(type='bool', default=False, aliases=['ssl']),
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'),
validate_certs=dict(type='bool', default=True),
validate_cert_hostname=dict(type='bool', default=False),
ca_path=dict(type='path'),
)
module_args.update(api_argument_spec())
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)
check_has_library(self.module)
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['tls'],
self.module.params['validate_certs'],
self.module.params['validate_cert_hostname'],
self.module.params['ca_path'],
)
self.api = create_api(self.module)
self.path = self.module.params['path'].split()
self.add = self.module.params['add']
@ -497,49 +428,6 @@ class ROS_api_module:
self.result['message'].append("%s" % e)
self.return_result(False, False)
def ros_api_connect(self, username, password, host, port, use_tls, validate_certs, validate_cert_hostname, ca_path):
# connect to routeros api
conn_status = {"connection": {"username": username,
"hostname": host,
"port": port,
"ssl": use_tls,
"status": "Connected"}}
try:
if use_tls:
if not port:
port = 8729
conn_status["connection"]["port"] = port
ctx = ssl.create_default_context(cafile=ca_path)
wrap_context = ctx.wrap_socket
if not validate_certs:
ctx.check_hostname = False
ctx.verify_mode = ssl.CERT_NONE
elif not validate_cert_hostname:
ctx.check_hostname = False
else:
# Since librouteros doesn't pass server_hostname,
# we have to do this ourselves:
def wrap_context(*args, **kwargs):
kwargs.pop('server_hostname', None)
return ctx.wrap_socket(*args, server_hostname=host, **kwargs)
api = connect(username=username,
password=password,
host=host,
ssl_wrapper=wrap_context,
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():