Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

api_modify/api_info: add restrict option #305

Merged
merged 8 commits into from
Aug 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions changelogs/fragments/305-api-restrict.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
minor_changes:
- "api_info - allow to restrict the output by limiting fields to specific values with the new ``restrict`` option (https://github.com/ansible-collections/community.routeros/pull/305)."
- "api_modify - allow to restrict what is updated by limiting fields to specific values with the new ``restrict`` option (https://github.com/ansible-collections/community.routeros/pull/305)."
41 changes: 41 additions & 0 deletions plugins/doc_fragments/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,3 +95,44 @@ class ModuleDocFragment(object):
- ref: ansible_collections.community.routeros.docsite.api-guide
description: How to connect to RouterOS devices with the RouterOS API
'''

RESTRICT = r'''
options:
restrict:
type: list
elements: dict
suboptions:
field:
description:
- The field whose values to restrict.
required: true
type: str
match_disabled:
description:
- Whether disabled or not provided values should match.
type: bool
default: false
values:
description:
- The values of the field to limit to.
- >-
Note that the types of the values are important. If you provide a string V("0"),
and librouteros converts the value returned by the API to the integer V(0),
then this will not match. If you are not sure, better include both variants:
both the string and the integer.
type: list
elements: raw
regex:
description:
- A regular expression matching values of the field to limit to.
- Note that all values will be converted to strings before matching.
- It is not possible to match disabled values with regular expressions.
felixfontein marked this conversation as resolved.
Show resolved Hide resolved
Set O(restrict[].match_disabled=true) if you also want to match disabled values.
type: str
invert:
description:
- Invert the condition. This affects O(restrict[].match_disabled), O(restrict[].values),
and O(restrict[].regex).
type: bool
default: false
'''
102 changes: 102 additions & 0 deletions plugins/module_utils/_api_helper.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2022, Felix Fontein (@felixfontein) <[email protected]>
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later

# The data inside here is private to this collection. If you use this from outside the collection,
# you are on your own. There can be random changes to its format even in bugfix releases!

from __future__ import absolute_import, division, print_function
__metaclass__ = type

import re

from ansible.module_utils.common.text.converters import to_text


def validate_and_prepare_restrict(module, path_info):
restrict = module.params['restrict']
if restrict is None:
return None
restrict_data = []
for rule in restrict:
field = rule['field']
if field.startswith('!'):
module.fail_json(msg='restrict: the field name "{0}" must not start with "!"'.format(field))
f = path_info.fields.get(field)
if f is None:
module.fail_json(msg='restrict: the field "{0}" does not exist for this path'.format(field))

new_rule = dict(
field=field,
match_disabled=rule['match_disabled'],
invert=rule['invert'],
)
if rule['values'] is not None:
new_rule['values'] = rule['values']
if rule['regex'] is not None:
regex = rule['regex']
try:
new_rule['regex'] = re.compile(regex)
new_rule['regex_source'] = regex
except Exception as exc:
module.fail_json(msg='restrict: invalid regular expression "{0}": {1}'.format(regex, exc))
restrict_data.append(new_rule)
return restrict_data


def _value_to_str(value):
if value is None:
return None
value_str = to_text(value)
if isinstance(value, bool):
value_str = value_str.lower()
return value_str


def _test_rule_except_invert(value, rule):
if value is None and rule['match_disabled']:
return True
if 'values' in rule and value in rule['values']:
return True
if 'regex' in rule and value is not None and rule['regex'].match(_value_to_str(value)):
return True
return False


def restrict_entry_accepted(entry, path_info, restrict_data):
if restrict_data is None:
return True
for rule in restrict_data:
# Obtain field and value
field = rule['field']
field_info = path_info.fields[field]
value = entry.get(field)
if value is None:
value = field_info.default
if field not in entry and field_info.absent_value:
value = field_info.absent_value

# Check
matches_rule = _test_rule_except_invert(value, rule)
if rule['invert']:
matches_rule = not matches_rule
if not matches_rule:
return False
return True


def restrict_argument_spec():
return dict(
restrict=dict(
type='list',
elements='dict',
options=dict(
field=dict(type='str', required=True),
match_disabled=dict(type='bool', default=False),
values=dict(type='list', elements='raw'),
regex=dict(type='str'),
invert=dict(type='bool', default=False),
),
),
)
27 changes: 27 additions & 0 deletions plugins/modules/api_info.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
L(create an issue in the community.routeros Issue Tracker,https://github.com/ansible-collections/community.routeros/issues/).
extends_documentation_fragment:
- community.routeros.api
- community.routeros.api.restrict
- community.routeros.attributes
- community.routeros.attributes.actiongroup_api
- community.routeros.attributes.info_module
Expand Down Expand Up @@ -301,6 +302,10 @@
type: bool
default: false
version_added: 2.10.0
restrict:
description:
- Restrict output to entries matching the following criteria.
version_added: 2.18.0
seealso:
- module: community.routeros.api
- module: community.routeros.api_facts
Expand All @@ -318,6 +323,18 @@
path: ip address
register: ip_addresses

- name: Print data for IP addresses
ansible.builtin.debug:
var: ip_addresses.result

- name: Get IP addresses
community.routeros.api_info:
hostname: "{{ hostname }}"
password: "{{ password }}"
username: "{{ username }}"
path: ip address
register: ip_addresses

- name: Print data for IP addresses
ansible.builtin.debug:
var: ip_addresses.result
Expand Down Expand Up @@ -358,6 +375,12 @@
split_path,
)

from ansible_collections.community.routeros.plugins.module_utils._api_helper import (
restrict_argument_spec,
restrict_entry_accepted,
validate_and_prepare_restrict,
)

try:
from librouteros.exceptions import LibRouterosError
except Exception:
Expand All @@ -383,6 +406,7 @@ def main():
include_read_only=dict(type='bool', default=False),
)
module_args.update(api_argument_spec())
module_args.update(restrict_argument_spec())

module = AnsibleModule(
argument_spec=module_args,
Expand Down Expand Up @@ -411,6 +435,7 @@ def main():
include_dynamic = module.params['include_dynamic']
include_builtin = module.params['include_builtin']
include_read_only = module.params['include_read_only']
restrict_data = validate_and_prepare_restrict(module, path_info)
try:
api_path = compose_api_path(api, path)

Expand All @@ -423,6 +448,8 @@ def main():
if not include_builtin:
if entry.get('builtin', False):
continue
if not restrict_entry_accepted(entry, path_info, restrict_data):
continue
if not unfiltered:
for k in list(entry):
if k == '.id':
Expand Down
Loading
Loading