From ce855f66c5a3b4febf7078be78cda58bc86955db Mon Sep 17 00:00:00 2001 From: John Estabrook Date: Mon, 7 Apr 2025 12:22:20 -0500 Subject: T7321: normalize formatting --- python/vyos/component_version.py | 55 +++++++++++++++++++++++++++------------- 1 file changed, 38 insertions(+), 17 deletions(-) (limited to 'python') diff --git a/python/vyos/component_version.py b/python/vyos/component_version.py index 94215531d..85705bb24 100644 --- a/python/vyos/component_version.py +++ b/python/vyos/component_version.py @@ -49,7 +49,9 @@ DEFAULT_CONFIG_PATH = os.path.join(directories['config'], 'config.boot') REGEX_WARN_VYOS = r'(// Warning: Do not remove the following line.)' REGEX_WARN_VYATTA = r'(/\* Warning: Do not remove the following line. \*/)' REGEX_COMPONENT_VERSION_VYOS = r'// vyos-config-version:\s+"([\w@:-]+)"\s*' -REGEX_COMPONENT_VERSION_VYATTA = r'/\* === vyatta-config-version:\s+"([\w@:-]+)"\s+=== \*/' +REGEX_COMPONENT_VERSION_VYATTA = ( + r'/\* === vyatta-config-version:\s+"([\w@:-]+)"\s+=== \*/' +) REGEX_RELEASE_VERSION_VYOS = r'// Release version:\s+(\S*)\s*' REGEX_RELEASE_VERSION_VYATTA = r'/\* Release version:\s+(\S*)\s*\*/' @@ -62,16 +64,31 @@ CONFIG_FILE_VERSION = """\ warn_filter_vyos = re.compile(REGEX_WARN_VYOS) warn_filter_vyatta = re.compile(REGEX_WARN_VYATTA) -regex_filter = { 'vyos': dict(zip(['component', 'release'], - [re.compile(REGEX_COMPONENT_VERSION_VYOS), - re.compile(REGEX_RELEASE_VERSION_VYOS)])), - 'vyatta': dict(zip(['component', 'release'], - [re.compile(REGEX_COMPONENT_VERSION_VYATTA), - re.compile(REGEX_RELEASE_VERSION_VYATTA)])) } +regex_filter = { + 'vyos': dict( + zip( + ['component', 'release'], + [ + re.compile(REGEX_COMPONENT_VERSION_VYOS), + re.compile(REGEX_RELEASE_VERSION_VYOS), + ], + ) + ), + 'vyatta': dict( + zip( + ['component', 'release'], + [ + re.compile(REGEX_COMPONENT_VERSION_VYATTA), + re.compile(REGEX_RELEASE_VERSION_VYATTA), + ], + ) + ), +} + @dataclass class VersionInfo: - component: Optional[dict[str,int]] = None + component: Optional[dict[str, int]] = None release: str = get_version() vintage: str = 'vyos' config_body: Optional[str] = None @@ -84,8 +101,9 @@ class VersionInfo: return bool(self.config_body is None) def update_footer(self): - f = CONFIG_FILE_VERSION.format(component_to_string(self.component), - self.release) + f = CONFIG_FILE_VERSION.format( + component_to_string(self.component), self.release + ) self.footer_lines = f.splitlines() def update_syntax(self): @@ -121,13 +139,16 @@ class VersionInfo: except Exception as e: raise ValueError(e) from e + def component_to_string(component: dict) -> str: - l = [f'{k}@{v}' for k, v in sorted(component.items(), key=lambda x: x[0])] + l = [f'{k}@{v}' for k, v in sorted(component.items(), key=lambda x: x[0])] # noqa: E741 return ':'.join(l) + def component_from_string(string: str) -> dict: return {k: int(v) for k, v in re.findall(r'([\w,-]+)@(\d+)', string)} + def version_info_from_file(config_file) -> VersionInfo: """Return config file component and release version info.""" version_info = VersionInfo() @@ -166,27 +187,27 @@ def version_info_from_file(config_file) -> VersionInfo: return version_info + def version_info_from_system() -> VersionInfo: """Return system component and release version info.""" d = component_version() sort_d = dict(sorted(d.items(), key=lambda x: x[0])) - version_info = VersionInfo( - component = sort_d, - release = get_version(), - vintage = 'vyos' - ) + version_info = VersionInfo(component=sort_d, release=get_version(), vintage='vyos') return version_info + def version_info_copy(v: VersionInfo) -> VersionInfo: """Make a copy of dataclass.""" return replace(v) + def version_info_prune_component(x: VersionInfo, y: VersionInfo) -> VersionInfo: """In place pruning of component keys of x not in y.""" if x.component is None or y.component is None: return - x.component = { k: v for k,v in x.component.items() if k in y.component } + x.component = {k: v for k, v in x.component.items() if k in y.component} + def add_system_version(config_str: str = None, out_file: str = None): """Wrap config string with system version and write to out_file. -- cgit v1.2.3 From 249f3d52d7585108fa7acf1c73a1dde000bd95b9 Mon Sep 17 00:00:00 2001 From: John Estabrook Date: Mon, 7 Apr 2025 12:26:42 -0500 Subject: T7321: add append version util --- python/vyos/component_version.py | 8 ++++++++ 1 file changed, 8 insertions(+) (limited to 'python') diff --git a/python/vyos/component_version.py b/python/vyos/component_version.py index 85705bb24..81d986658 100644 --- a/python/vyos/component_version.py +++ b/python/vyos/component_version.py @@ -223,3 +223,11 @@ def add_system_version(config_str: str = None, out_file: str = None): version_info.write(out_file) else: sys.stdout.write(version_info.write_string()) + + +def append_system_version(file: str): + """Append system version data to existing file""" + version_info = version_info_from_system() + version_info.update_footer() + with open(file, 'a') as f: + f.write(version_info.write_string()) -- cgit v1.2.3 From 2b1010e1259da89a674c5c06e71bda6e8904b0d0 Mon Sep 17 00:00:00 2001 From: John Estabrook Date: Mon, 7 Apr 2025 15:39:54 -0500 Subject: T7321: translate enums by value instead of name --- python/vyos/proto/vyconf_client.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'python') diff --git a/python/vyos/proto/vyconf_client.py b/python/vyos/proto/vyconf_client.py index f34549309..b385f0951 100644 --- a/python/vyos/proto/vyconf_client.py +++ b/python/vyos/proto/vyconf_client.py @@ -52,7 +52,9 @@ def request_to_msg(req: vyconf_proto.RequestEnvelope) -> vyconf_pb2.RequestEnvel def msg_to_response(msg: vyconf_pb2.Response) -> vyconf_proto.Response: # pylint: disable=no-member - d = MessageToDict(msg, preserving_proto_field_name=True) + d = MessageToDict( + msg, preserving_proto_field_name=True, use_integers_for_enums=True + ) response = vyconf_proto.Response(**d) return response -- cgit v1.2.3 From 6efc149e3246919b39cc831c8fa5110c19c68ace Mon Sep 17 00:00:00 2001 From: John Estabrook Date: Mon, 7 Apr 2025 15:40:46 -0500 Subject: T7321: add VyconfSession class and methods Encapsulation of standard config session functions, to replace legacy versions in configsession.py. --- python/vyos/vyconf_session.py | 99 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 99 insertions(+) create mode 100644 python/vyos/vyconf_session.py (limited to 'python') diff --git a/python/vyos/vyconf_session.py b/python/vyos/vyconf_session.py new file mode 100644 index 000000000..cff3769fb --- /dev/null +++ b/python/vyos/vyconf_session.py @@ -0,0 +1,99 @@ +# Copyright 2025 VyOS maintainers and contributors +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library 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 +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see . +# +# + +import tempfile +import shutil + +from vyos.proto import vyconf_client +from vyos.migrate import ConfigMigrate +from vyos.migrate import ConfigMigrateError +from vyos.component_version import append_system_version + + +def output(o): + out = '' + for res in (o.output, o.error, o.warning): + if res is not None: + out = out + res + return out + + +class VyconfSession: + def __init__(self, token: str = None): + if token is None: + out = vyconf_client.send_request('setup_session') + self.__token = out.output + else: + self.__token = token + + def set(self, path: list[str]) -> tuple[str, int]: + out = vyconf_client.send_request('set', token=self.__token, path=path) + return output(out), out.status + + def delete(self, path: list[str]) -> tuple[str, int]: + out = vyconf_client.send_request('delete', token=self.__token, path=path) + return output(out), out.status + + def commit(self) -> tuple[str, int]: + out = vyconf_client.send_request('commit', token=self.__token) + return output(out), out.status + + def discard(self) -> tuple[str, int]: + out = vyconf_client.send_request('discard', token=self.__token) + return output(out), out.status + + def session_changed(self) -> bool: + out = vyconf_client.send_request('session_changed', token=self.__token) + return not bool(out.status) + + def load_config(self, file: str, migrate: bool = False) -> tuple[str, int]: + # pylint: disable=consider-using-with + if migrate: + tmp = tempfile.NamedTemporaryFile() + shutil.copy2(file, tmp.name) + config_migrate = ConfigMigrate(tmp.name) + try: + config_migrate.run() + except ConfigMigrateError as e: + tmp.close() + return repr(e), 1 + file = tmp.name + else: + tmp = '' + + out = vyconf_client.send_request('load', token=self.__token, location=file) + if tmp: + tmp.close() + + return output(out), out.status + + def save_config(self, file: str, append_version: bool = False) -> tuple[str, int]: + out = vyconf_client.send_request('save', token=self.__token, location=file) + if append_version: + append_system_version(file) + return output(out), out.status + + def show_config(self, path: list[str] = None) -> tuple[str, int]: + if path is None: + path = [] + out = vyconf_client.send_request('show_config', token=self.__token, path=path) + return output(out), out.status + + def __del__(self): + out = vyconf_client.send_request('teardown', token=self.__token) + if out.status: + print(f'Could not tear down session {self.__token}: {output(out)}') -- cgit v1.2.3 From 957eb2d49edeca10dadfd0204bdf54edf7c09601 Mon Sep 17 00:00:00 2001 From: John Estabrook Date: Fri, 11 Apr 2025 12:23:17 -0500 Subject: T7321: add decorator to raise named exception on error --- python/vyos/vyconf_session.py | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) (limited to 'python') diff --git a/python/vyos/vyconf_session.py b/python/vyos/vyconf_session.py index cff3769fb..506095625 100644 --- a/python/vyos/vyconf_session.py +++ b/python/vyos/vyconf_session.py @@ -17,6 +17,8 @@ import tempfile import shutil +from functools import wraps +from typing import Type from vyos.proto import vyconf_client from vyos.migrate import ConfigMigrate @@ -33,25 +35,44 @@ def output(o): class VyconfSession: - def __init__(self, token: str = None): + def __init__(self, token: str = None, on_error: Type[Exception] = None): if token is None: out = vyconf_client.send_request('setup_session') self.__token = out.output else: self.__token = token + self.on_error = on_error + + @staticmethod + def raise_exception(f): + @wraps(f) + def wrapped(self, *args, **kwargs): + if self.on_error is None: + return f(self, *args, **kwargs) + o, e = f(self, *args, **kwargs) + if e: + raise self.on_error(o) + return o, e + + return wrapped + + @raise_exception def set(self, path: list[str]) -> tuple[str, int]: out = vyconf_client.send_request('set', token=self.__token, path=path) return output(out), out.status + @raise_exception def delete(self, path: list[str]) -> tuple[str, int]: out = vyconf_client.send_request('delete', token=self.__token, path=path) return output(out), out.status + @raise_exception def commit(self) -> tuple[str, int]: out = vyconf_client.send_request('commit', token=self.__token) return output(out), out.status + @raise_exception def discard(self) -> tuple[str, int]: out = vyconf_client.send_request('discard', token=self.__token) return output(out), out.status @@ -60,6 +81,7 @@ class VyconfSession: out = vyconf_client.send_request('session_changed', token=self.__token) return not bool(out.status) + @raise_exception def load_config(self, file: str, migrate: bool = False) -> tuple[str, int]: # pylint: disable=consider-using-with if migrate: @@ -81,12 +103,14 @@ class VyconfSession: return output(out), out.status + @raise_exception def save_config(self, file: str, append_version: bool = False) -> tuple[str, int]: out = vyconf_client.send_request('save', token=self.__token, location=file) if append_version: append_system_version(file) return output(out), out.status + @raise_exception def show_config(self, path: list[str] = None) -> tuple[str, int]: if path is None: path = [] -- cgit v1.2.3 From 99720f43b5ffba6ba455f1c16a7e9062df6e9642 Mon Sep 17 00:00:00 2001 From: John Estabrook Date: Fri, 11 Apr 2025 12:26:23 -0500 Subject: T7321: expose vyconfd client functions in configsession --- python/vyos/configsession.py | 55 +++++++++++++++++++++++++++++++++++++------- 1 file changed, 47 insertions(+), 8 deletions(-) (limited to 'python') diff --git a/python/vyos/configsession.py b/python/vyos/configsession.py index 90b96b88c..a3be29881 100644 --- a/python/vyos/configsession.py +++ b/python/vyos/configsession.py @@ -21,6 +21,10 @@ import subprocess from vyos.defaults import directories from vyos.utils.process import is_systemd_service_running from vyos.utils.dict import dict_to_paths +from vyos.utils.boot import boot_configuration_complete +from vyos.vyconf_session import VyconfSession + +vyconf_backend = False CLI_SHELL_API = '/bin/cli-shell-api' SET = '/opt/vyatta/sbin/my_set' @@ -165,6 +169,11 @@ class ConfigSession(object): self.__run_command([CLI_SHELL_API, 'setupSession']) + if vyconf_backend and boot_configuration_complete(): + self._vyconf_session = VyconfSession(on_error=ConfigSessionError) + else: + self._vyconf_session = None + def __del__(self): try: output = ( @@ -209,7 +218,10 @@ class ConfigSession(object): value = [] else: value = [value] - self.__run_command([SET] + path + value) + if self._vyconf_session is None: + self.__run_command([SET] + path + value) + else: + self._vyconf_session.set(path + value) def set_section(self, path: list, d: dict): try: @@ -223,7 +235,10 @@ class ConfigSession(object): value = [] else: value = [value] - self.__run_command([DELETE] + path + value) + if self._vyconf_session is None: + self.__run_command([DELETE] + path + value) + else: + self._vyconf_session.delete(path + value) def load_section(self, path: list, d: dict): try: @@ -261,20 +276,34 @@ class ConfigSession(object): self.__run_command([COMMENT] + path + value) def commit(self): - out = self.__run_command([COMMIT]) + if self._vyconf_session is None: + out = self.__run_command([COMMIT]) + else: + out, _ = self._vyconf_session.commit() + return out def discard(self): - self.__run_command([DISCARD]) + if self._vyconf_session is None: + self.__run_command([DISCARD]) + else: + out, _ = self._vyconf_session.discard() def show_config(self, path, format='raw'): - config_data = self.__run_command(SHOW_CONFIG + path) + if self._vyconf_session is None: + config_data = self.__run_command(SHOW_CONFIG + path) + else: + config_data, _ = self._vyconf_session.show_config() if format == 'raw': return config_data def load_config(self, file_path): - out = self.__run_command(LOAD_CONFIG + [file_path]) + if self._vyconf_session is None: + out = self.__run_command(LOAD_CONFIG + [file_path]) + else: + out, _ = self._vyconf_session.load_config(file=file_path) + return out def load_explicit(self, file_path): @@ -287,11 +316,21 @@ class ConfigSession(object): raise ConfigSessionError(e) from e def migrate_and_load_config(self, file_path): - out = self.__run_command(MIGRATE_LOAD_CONFIG + [file_path]) + if self._vyconf_session is None: + out = self.__run_command(MIGRATE_LOAD_CONFIG + [file_path]) + else: + out, _ = self._vyconf_session.load_config(file=file_path, migrate=True) + return out def save_config(self, file_path): - out = self.__run_command(SAVE_CONFIG + [file_path]) + if self._vyconf_session is None: + out = self.__run_command(SAVE_CONFIG + [file_path]) + else: + out, _ = self._vyconf_session.save_config( + file=file_path, append_version=True + ) + return out def install_image(self, url): -- cgit v1.2.3