From 8b2d2a80c0f9272d80ee4d90077978c9e43021b9 Mon Sep 17 00:00:00 2001 From: John Estabrook Date: Mon, 4 May 2026 14:33:07 -0500 Subject: T8502: add list of global config-sync exclude paths --- python/vyos/defaults.py | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'python') diff --git a/python/vyos/defaults.py b/python/vyos/defaults.py index 78721c0d2..22aa1f62a 100644 --- a/python/vyos/defaults.py +++ b/python/vyos/defaults.py @@ -106,3 +106,7 @@ reference_tree_cache = '/usr/share/vyos/reftree.cache' activation_list = os.path.join(directories['config'], 'activation-list') activation_init = os.path.join(directories['data'], 'activation-init') activation_hint = os.path.join(directories['data'], '.activation_hint') + +config_sync_exclusion_list = os.path.join( + directories['data'], 'config-sync-exclude.json' +) -- cgit v1.2.3 From bc541bbcc3e7da924ab1d21aed360eba09621e0b Mon Sep 17 00:00:00 2001 From: John Estabrook Date: Tue, 5 May 2026 13:50:13 -0500 Subject: T8502: add library support for config-sync exclude paths --- python/vyos/configtree.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) (limited to 'python') diff --git a/python/vyos/configtree.py b/python/vyos/configtree.py index d92137c14..47e56bc46 100644 --- a/python/vyos/configtree.py +++ b/python/vyos/configtree.py @@ -667,6 +667,28 @@ def mask_exclusive(left, right, libpath=LIBPATH): return tree +def delete_tree_from_masks( + config_tree: ConfigTree, include_mask: ConfigTree, exclude_mask: ConfigTree +): + masked_inc = mask_inclusive(config_tree, include_mask) + # Here we want the reversed stand-alone exclusion/inclusion. + # This simplifies definition of delete paths as (delete) + # difference between the two trees of config data. + masked_upper_bound = mask_exclusive(config_tree, include_mask) + masked_lower_bound = mask_inclusive(config_tree, exclude_mask) + masked_exc = union(masked_upper_bound, masked_lower_bound) + + ret = DiffTree(masked_inc, masked_exc) + return ret.delete + + +def delete_dict_from_masks( + config_tree: ConfigTree, include_mask: ConfigTree, exclude_mask: ConfigTree +): + ret = delete_tree_from_masks(config_tree, include_mask, exclude_mask) + return json.loads(ret.to_json()) + + def subtree_from_partial( config_tree: ConfigTree, path: list[str], -- cgit v1.2.3 From 9b6e4c75c8a362afa82c711a4254f38e8986662a Mon Sep 17 00:00:00 2001 From: John Estabrook Date: Mon, 4 May 2026 14:34:57 -0500 Subject: T8502: add server support for config-sync excluded paths --- python/vyos/configsession.py | 34 ++++++++++++++++++++++++++++------ src/services/api/rest/routers.py | 9 +++++---- 2 files changed, 33 insertions(+), 10 deletions(-) (limited to 'python') diff --git a/python/vyos/configsession.py b/python/vyos/configsession.py index 8fee8eca1..f2abd3a5b 100644 --- a/python/vyos/configsession.py +++ b/python/vyos/configsession.py @@ -16,6 +16,7 @@ import os import re import sys +import json import weakref import subprocess from tempfile import NamedTemporaryFile @@ -31,6 +32,9 @@ from vyos.vyconf_session import VyconfSession from vyos.base import Warning as Warn from vyos.defaults import DEFAULT_COMMIT_CONFIRM_MINUTES from vyos.configtree import ConfigTree +from vyos.configtree import ConfigTreeError +from vyos.configtree import delete_dict_from_masks +from vyos.derivedtree import subtree_from_list_of_partial_paths # type of config file path or configtree ConfigObj: TypeAlias = Union[str, ConfigTree] @@ -302,15 +306,33 @@ class ConfigSession(object): except (ValueError, ConfigSessionError) as e: raise ConfigSessionError(e) - def load_section_tree(self, mask: dict, d: dict): + def load_section_tree( + self, config_tree: ConfigTree, mask_dict: dict, config_dict: dict + ): + if ( + not mask_dict + or 'inclusive' not in mask_dict + or 'exclusive' not in mask_dict + ): + raise ConfigSessionError( + "Missing mask data can damage the config: expected keys 'inclusive' and 'exclusive'" + ) try: - if mask: - for p in dict_to_paths(mask): + mask_in = ConfigTree(internal_string=mask_dict['inclusive']) + + mask_ex_list = json.loads(mask_dict['exclusive']) + mask_ex = subtree_from_list_of_partial_paths(config_tree, mask_ex_list) + + delete_dict = delete_dict_from_masks(config_tree, mask_in, mask_ex) + + if delete_dict: + for p in dict_to_paths(delete_dict): self.delete(p) - if d: - for p in dict_to_paths(d): + + if config_dict: + for p in dict_to_paths(config_dict): self.set(p) - except (ValueError, ConfigSessionError) as e: + except (ValueError, ConfigSessionError, ConfigTreeError) as e: raise ConfigSessionError(e) def comment(self, path, value=None): diff --git a/src/services/api/rest/routers.py b/src/services/api/rest/routers.py index 058bcc20d..fe67d4612 100644 --- a/src/services/api/rest/routers.py +++ b/src/services/api/rest/routers.py @@ -421,8 +421,8 @@ def _execute_configure_op( section = c.section elif isinstance(c, BaseConfigSectionTreeModel): - mask = c.mask - config = c.config + mask_dict = c.mask + config_dict = c.config if isinstance(c, BaseConfigureModel): if op == 'set': @@ -448,9 +448,10 @@ def _execute_configure_op( elif isinstance(c, BaseConfigSectionTreeModel): if op == 'set': - session.set_section_tree(config) + session.set_section_tree(config_dict) elif op == 'load': - session.load_section_tree(mask, config) + config_tree = config.get_config_tree() + session.load_section_tree(config_tree, mask_dict, config_dict) else: raise op_error # end for -- cgit v1.2.3 From 9ff29049d42dbd40ede1c4507bc6598e0403ce46 Mon Sep 17 00:00:00 2001 From: John Estabrook Date: Wed, 27 May 2026 08:16:34 -0500 Subject: T8916: allow passing ReferenceTree instance to subtree_from_partial For use in nosetests or other, allow passing ReferenceTree from an internal cache in a non-standard location. --- python/vyos/derivedtree.py | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) (limited to 'python') diff --git a/python/vyos/derivedtree.py b/python/vyos/derivedtree.py index 0a57921b2..dc46b8eb3 100644 --- a/python/vyos/derivedtree.py +++ b/python/vyos/derivedtree.py @@ -25,7 +25,10 @@ class DerivedTreeError(Exception): def subtree_from_list_of_partial_paths( - ctree: ConfigTree, paths: list[list[str]], accumulator: ConfigTree = None + ctree: ConfigTree, + paths: list[list[str]], + accumulator: ConfigTree = None, + reference_tree: ReferenceTree = None, ): """Return the union of subtrees of the ConfigTree argument matching each of the 'partial' paths. A partial path is one that may or may not @@ -33,19 +36,23 @@ def subtree_from_list_of_partial_paths( values that apply. An existing subtree may be passed as the initial value of accumulator. + + For testing or use outside of the canonical environment, an instance of + the ReferenceTree may be passed from an alternative cache location. """ - if accumulator: + if reference_tree is None: + reference_tree = ReferenceTree() + + if accumulator is not None: if not isinstance(accumulator, ConfigTree): raise TypeError("Argument 'accumulator' must be an instance of ConfigTree") else: accumulator = ConfigTree('') - rtree = ReferenceTree() - errors = [] for path in paths: try: - accumulator = subtree_from_partial(ctree, path, rtree, accumulator) + accumulator = subtree_from_partial(ctree, path, reference_tree, accumulator) except ConfigTreeError as e: errors.append(str(e)) continue -- cgit v1.2.3