From 36c475ec3524739f9ae49420e60a57a5266fa575 Mon Sep 17 00:00:00 2001 From: Daniil Baturin Date: Thu, 20 Oct 2022 09:14:53 -0400 Subject: T4765: normalize dict fields in op mode ouputs --- python/vyos/opmode.py | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) (limited to 'python/vyos/opmode.py') diff --git a/python/vyos/opmode.py b/python/vyos/opmode.py index 7e3545c87..ac9c0c353 100644 --- a/python/vyos/opmode.py +++ b/python/vyos/opmode.py @@ -44,6 +44,13 @@ class PermissionDenied(Error): """ pass +class InternalError(Error): + """ Any situation when VyOS detects that it could not perform + an operation correctly due to logic errors in its own code + or errors in underlying software. + """ + pass + def _is_op_mode_function_name(name): if re.match(r"^(show|clear|reset|restart)", name): @@ -93,6 +100,39 @@ def _get_arg_type(t): else: return t +def _normalize_field_name(name): + # Replace all separators with underscores + name = re.sub(r'(\s|[\(\)\[\]\{\}\-\.\,:\"\'\`])+', '_', name) + + # Replace specific characters with textual descriptions + name = re.sub(r'@', '_at_', name) + name = re.sub(r'%', '_percentage_', name) + name = re.sub(r'~', '_tilde_', name) + + # Force all letters to lowercase + name = name.lower() + + # Remove leading and trailing underscores, if any + name = re.sub(r'(^(_+)(?=[^_])|_+$)', '', name) + + # Ensure there are only single underscores + name = re.sub(r'_+', '_', name) + + return name + +def _normalize_field_names(old_dict): + new_dict = {} + + for key in old_dict: + new_key = _normalize_field_name(key) + new_dict[new_key] = old_dict[key] + + # Sanity check + if len(old_dict) != len(new_dict): + raise InternalError("Dictionary fields do not allow unique normalization") + else: + return new_dict + def run(module): from argparse import ArgumentParser @@ -145,6 +185,7 @@ def run(module): # they may return human-formatted output # or a raw dict that we need to serialize in JSON for printing res = func(**args) + res = _normalize_field_names(res) if not args["raw"]: return res else: -- cgit v1.2.3 From 40cf5f7c1b8d8ae27b5c404807294f8a2a76842d Mon Sep 17 00:00:00 2001 From: John Estabrook Date: Thu, 20 Oct 2022 16:25:51 -0500 Subject: T4765: normalize fields only if 'raw' is true; output must be dict --- python/vyos/opmode.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'python/vyos/opmode.py') diff --git a/python/vyos/opmode.py b/python/vyos/opmode.py index ac9c0c353..5f9c2c1ce 100644 --- a/python/vyos/opmode.py +++ b/python/vyos/opmode.py @@ -185,10 +185,12 @@ def run(module): # they may return human-formatted output # or a raw dict that we need to serialize in JSON for printing res = func(**args) - res = _normalize_field_names(res) if not args["raw"]: return res else: + if not isinstance(res, dict): + raise InternalError("'raw' output of 'show_*' command must be a dict") + res = _normalize_field_names(res) from json import dumps return dumps(res, indent=4) else: -- cgit v1.2.3 From b6d2e0a4b08c81814cb2d9b5b611cbc3fc31dbeb Mon Sep 17 00:00:00 2001 From: create with ansible Date: Fri, 21 Oct 2022 11:50:17 -0400 Subject: T4765: support list and primitives in op mode output normalization --- python/vyos/opmode.py | 14 ++++++++++---- src/tests/test_op_mode.py | 25 +++++++++++++++++++++++-- 2 files changed, 33 insertions(+), 6 deletions(-) (limited to 'python/vyos/opmode.py') diff --git a/python/vyos/opmode.py b/python/vyos/opmode.py index 5f9c2c1ce..c9827d634 100644 --- a/python/vyos/opmode.py +++ b/python/vyos/opmode.py @@ -120,12 +120,12 @@ def _normalize_field_name(name): return name -def _normalize_field_names(old_dict): +def _normalize_dict_field_names(old_dict): new_dict = {} for key in old_dict: new_key = _normalize_field_name(key) - new_dict[new_key] = old_dict[key] + new_dict[new_key] = _normalize_field_names(old_dict[key]) # Sanity check if len(old_dict) != len(new_dict): @@ -133,6 +133,14 @@ def _normalize_field_names(old_dict): else: return new_dict +def _normalize_field_names(value): + if isinstance(value, dict): + return _normalize_dict_field_names(value) + elif isinstance(value, list): + return list(map(lambda v: _normalize_field_names(v), value)) + else: + return value + def run(module): from argparse import ArgumentParser @@ -188,8 +196,6 @@ def run(module): if not args["raw"]: return res else: - if not isinstance(res, dict): - raise InternalError("'raw' output of 'show_*' command must be a dict") res = _normalize_field_names(res) from json import dumps return dumps(res, indent=4) diff --git a/src/tests/test_op_mode.py b/src/tests/test_op_mode.py index 4786357c5..90963b3c5 100644 --- a/src/tests/test_op_mode.py +++ b/src/tests/test_op_mode.py @@ -37,8 +37,29 @@ class TestVyOSOpMode(TestCase): with self.assertRaises(vyos.opmode.InternalError): _normalize_field_names(data) - def test_dict_fields_normalization(self): + def test_dict_fields_normalization_simple_dict(self): from vyos.opmode import _normalize_field_names - data = {"foo bar": True, "bar-baz": False} + data = {"foo bar": True, "Bar-Baz": False} self.assertEqual(_normalize_field_names(data), {"foo_bar": True, "bar_baz": False}) + + def test_dict_fields_normalization_nested_dict(self): + from vyos.opmode import _normalize_field_names + + data = {"foo bar": True, "bar-baz": {"baz-quux": {"quux-xyzzy": False}}} + self.assertEqual(_normalize_field_names(data), + {"foo_bar": True, "bar_baz": {"baz_quux": {"quux_xyzzy": False}}}) + + def test_dict_fields_normalization_mixed(self): + from vyos.opmode import _normalize_field_names + + data = [{"foo bar": True, "bar-baz": [{"baz-quux": {"quux-xyzzy": [False]}}]}] + self.assertEqual(_normalize_field_names(data), + [{"foo_bar": True, "bar_baz": [{"baz_quux": {"quux_xyzzy": [False]}}]}]) + + def test_dict_fields_normalization_primitive(self): + from vyos.opmode import _normalize_field_names + + data = [1, False, "foo"] + self.assertEqual(_normalize_field_names(data), [1, False, "foo"]) + -- cgit v1.2.3 From fca46598415f0c6f11c272d6b384ac98500fd69d Mon Sep 17 00:00:00 2001 From: Daniil Baturin Date: Fri, 28 Oct 2022 11:04:51 -0400 Subject: T4765: handle non-string fields in the raw op mode output normalizer --- python/vyos/opmode.py | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'python/vyos/opmode.py') diff --git a/python/vyos/opmode.py b/python/vyos/opmode.py index c9827d634..727e118a8 100644 --- a/python/vyos/opmode.py +++ b/python/vyos/opmode.py @@ -101,6 +101,10 @@ def _get_arg_type(t): return t def _normalize_field_name(name): + # Convert the name to string if it is not + # (in some cases they may be numbers) + name = str(name) + # Replace all separators with underscores name = re.sub(r'(\s|[\(\)\[\]\{\}\-\.\,:\"\'\`])+', '_', name) -- cgit v1.2.3 From c6f7ef4b84b25bf8c35e3b0755d2e973a072ebd5 Mon Sep 17 00:00:00 2001 From: John Estabrook Date: Tue, 1 Nov 2022 11:20:57 -0500 Subject: op-mode: T4791: decamelize raw output of 'show_*' before normalization --- python/vyos/opmode.py | 2 ++ 1 file changed, 2 insertions(+) (limited to 'python/vyos/opmode.py') diff --git a/python/vyos/opmode.py b/python/vyos/opmode.py index 727e118a8..2e896c8e6 100644 --- a/python/vyos/opmode.py +++ b/python/vyos/opmode.py @@ -16,6 +16,7 @@ import re import sys import typing +from humps import decamelize class Error(Exception): @@ -200,6 +201,7 @@ def run(module): if not args["raw"]: return res else: + res = decamelize(res) res = _normalize_field_names(res) from json import dumps return dumps(res, indent=4) -- cgit v1.2.3 From 58057480e22712dc6d04396f8805d3db338bddfa Mon Sep 17 00:00:00 2001 From: John Estabrook Date: Fri, 18 Nov 2022 16:08:47 -0600 Subject: IPsec: T4828: raise op-mode error on incorrect value --- python/vyos/opmode.py | 6 ++++++ src/op_mode/ipsec.py | 17 ++++++----------- .../api/graphql/session/errors/op_mode_errors.py | 6 ++++-- 3 files changed, 16 insertions(+), 13 deletions(-) (limited to 'python/vyos/opmode.py') diff --git a/python/vyos/opmode.py b/python/vyos/opmode.py index 2e896c8e6..9dba8d30f 100644 --- a/python/vyos/opmode.py +++ b/python/vyos/opmode.py @@ -45,6 +45,12 @@ class PermissionDenied(Error): """ pass +class IncorrectValue(Error): + """ Requested operation is valid, but an argument provided has an + incorrect value, preventing successful completion. + """ + pass + class InternalError(Error): """ Any situation when VyOS detects that it could not perform an operation correctly due to logic errors in its own code diff --git a/src/op_mode/ipsec.py b/src/op_mode/ipsec.py index aaa0cec5a..83e4241d7 100755 --- a/src/op_mode/ipsec.py +++ b/src/op_mode/ipsec.py @@ -14,6 +14,7 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . +import os import re import sys @@ -138,6 +139,8 @@ def _get_formatted_output_sas(sas): def get_peer_connections(peer, tunnel, return_all = False): search = rf'^[\s]*({peer}-(tunnel-[\d]+|vti)).*' matches = [] + if not os.path.exists(SWANCTL_CONF): + raise vyos.opmode.UnconfiguredSubsystem("IPsec not initialized") with open(SWANCTL_CONF, 'r') as f: for line in f.readlines(): result = re.match(search, line) @@ -149,27 +152,19 @@ def get_peer_connections(peer, tunnel, return_all = False): def reset_peer(peer: str, tunnel:str): - if not peer: - print('Invalid peer, aborting') - return - conns = get_peer_connections(peer, tunnel, return_all = (not tunnel or tunnel == 'all')) if not conns: - print('Tunnel(s) not found, aborting') - return + raise vyos.opmode.IncorrectValue('Peer or tunnel(s) not found, aborting') - result = True for conn in conns: try: call(f'sudo /usr/sbin/ipsec down {conn}{{*}}', timeout = 10) call(f'sudo /usr/sbin/ipsec up {conn}', timeout = 10) except TimeoutExpired as e: - print(f'Timed out while resetting {conn}') - result = False - + raise vyos.opmode.InternalError(f'Timed out while resetting {conn}') - print('Peer reset result: ' + ('success' if result else 'failed')) + print('Peer reset result: success') def show_sa(raw: bool): diff --git a/src/services/api/graphql/session/errors/op_mode_errors.py b/src/services/api/graphql/session/errors/op_mode_errors.py index 7ba75455d..7bc1d1d81 100644 --- a/src/services/api/graphql/session/errors/op_mode_errors.py +++ b/src/services/api/graphql/session/errors/op_mode_errors.py @@ -3,11 +3,13 @@ op_mode_err_msg = { "UnconfiguredSubsystem": "subsystem is not configured or not running", "DataUnavailable": "data currently unavailable", - "PermissionDenied": "client does not have permission" + "PermissionDenied": "client does not have permission", + "IncorrectValue": "argument value is incorrect" } op_mode_err_code = { "UnconfiguredSubsystem": 2000, "DataUnavailable": 2001, - "PermissionDenied": 1003 + "PermissionDenied": 1003, + "IncorrectValue": 1002 } -- cgit v1.2.3 From e65fd4853996375687bd3094ec8338a1ad317b3d Mon Sep 17 00:00:00 2001 From: John Estabrook Date: Tue, 6 Dec 2022 14:08:51 -0600 Subject: opmode: T4770: add CommitInProgess error --- python/vyos/opmode.py | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'python/vyos/opmode.py') diff --git a/python/vyos/opmode.py b/python/vyos/opmode.py index 9dba8d30f..5ff768859 100644 --- a/python/vyos/opmode.py +++ b/python/vyos/opmode.py @@ -51,6 +51,12 @@ class IncorrectValue(Error): """ pass +class CommitInProgress(Error): + """ Requested operation is valid, but not possible at the time due + to a commit being in progress. + """ + pass + class InternalError(Error): """ Any situation when VyOS detects that it could not perform an operation correctly due to logic errors in its own code -- cgit v1.2.3