summaryrefslogtreecommitdiff
path: root/python
diff options
context:
space:
mode:
authorJohn Estabrook <jestabro@vyos.io>2026-05-28 08:40:57 -0500
committerGitHub <noreply@github.com>2026-05-28 08:40:57 -0500
commitd4bb72161387132475e68c9186419f0384c28b32 (patch)
tree2642e3042134e5d45dc888bac7fb3bfa27500f81 /python
parent07410437cde7b1df74cf62343a47549051279f08 (diff)
parent8aa9fe5d526e3a7432959e60216ca3363bde90fb (diff)
downloadvyos-1x-d4bb72161387132475e68c9186419f0384c28b32.tar.gz
vyos-1x-d4bb72161387132475e68c9186419f0384c28b32.zip
Merge pull request #5169 from jestabro/exclusive-mask-config-sync
T8502: Add exclusion mask to config-sync
Diffstat (limited to 'python')
-rw-r--r--python/vyos/configsession.py34
-rw-r--r--python/vyos/configtree.py22
-rw-r--r--python/vyos/defaults.py4
-rw-r--r--python/vyos/derivedtree.py17
4 files changed, 66 insertions, 11 deletions
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/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],
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'
+)
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