# configtree -- a standalone VyOS config file manipulation library (Python bindings) # Copyright (C) 2018-2022 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, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA import os import re import json from ctypes import cdll, c_char_p, c_void_p, c_int, c_bool LIBPATH = '/usr/lib/libvyosconfig.so.0' def escape_backslash(string: str) -> str: """Escape single backslashes in string that are not in escape sequence""" p = re.compile(r'(?= 1): return True else: return False def set_tag(self, path): check_path(path) path_str = " ".join(map(str, path)).encode() res = self.__set_tag(self.__config, path_str) if (res == 0): return True else: raise ConfigTreeError("Path [{}] doesn't exist".format(path_str)) def get_subtree(self, path, with_node=False): check_path(path) path_str = " ".join(map(str, path)).encode() res = self.__get_subtree(self.__config, path_str, with_node) subt = ConfigTree(address=res) return subt def show_diff(left, right, path=[], commands=False, libpath=LIBPATH): if left is None: left = ConfigTree(config_string='\n') if right is None: right = ConfigTree(config_string='\n') if not (isinstance(left, ConfigTree) and isinstance(right, ConfigTree)): raise TypeError("Arguments must be instances of ConfigTree") if path: if (not left.exists(path)) and (not right.exists(path)): raise ConfigTreeError(f"Path {path} doesn't exist") check_path(path) path_str = " ".join(map(str, path)).encode() __lib = cdll.LoadLibrary(libpath) __show_diff = __lib.show_diff __show_diff.argtypes = [c_bool, c_char_p, c_void_p, c_void_p] __show_diff.restype = c_char_p __get_error = __lib.get_error __get_error.argtypes = [] __get_error.restype = c_char_p res = __show_diff(commands, path_str, left._get_config(), right._get_config()) res = res.decode() if res == "#1@": msg = __get_error().decode() raise ConfigTreeError(msg) return res class DiffTree: def __init__(self, left, right, path=[], libpath=LIBPATH): if left is None: left = ConfigTree(config_string='\n') if right is None: right = ConfigTree(config_string='\n') if not (isinstance(left, ConfigTree) and isinstance(right, ConfigTree)): raise TypeError("Arguments must be instances of ConfigTree") if path: if not left.exists(path): raise ConfigTreeError(f"Path {path} doesn't exist in lhs tree") if not right.exists(path): raise ConfigTreeError(f"Path {path} doesn't exist in rhs tree") self.left = left self.right = right self.__lib = cdll.LoadLibrary(libpath) self.__diff_tree = self.__lib.diff_tree self.__diff_tree.argtypes = [c_char_p, c_void_p, c_void_p] self.__diff_tree.restype = c_void_p self.__trim_tree = self.__lib.trim_tree self.__trim_tree.argtypes = [c_void_p, c_void_p] self.__trim_tree.restype = c_void_p check_path(path) path_str = " ".join(map(str, path)).encode() res = self.__diff_tree(path_str, left._get_config(), right._get_config()) # full diff config_tree and python dict representation self.full = ConfigTree(address=res) self.dict = json.loads(self.full.to_json()) # config_tree sub-trees self.add = self.full.get_subtree(['add']) self.sub = self.full.get_subtree(['sub']) self.inter = self.full.get_subtree(['inter']) # trim sub(-tract) tree to get delete tree for commands ref = self.right.get_subtree(path, with_node=True) if path else self.right res = self.__trim_tree(self.sub._get_config(), ref._get_config()) self.delete = ConfigTree(address=res) def to_commands(self): add = self.add.to_commands() delete = self.delete.to_commands(op="delete") return delete + "\n" + add