diff options
Diffstat (limited to 'python/vyos/configtree.py')
-rw-r--r-- | python/vyos/configtree.py | 283 |
1 files changed, 283 insertions, 0 deletions
diff --git a/python/vyos/configtree.py b/python/vyos/configtree.py new file mode 100644 index 000000000..d8ffaca99 --- /dev/null +++ b/python/vyos/configtree.py @@ -0,0 +1,283 @@ +# configtree -- a standalone VyOS config file manipulation library (Python bindings) +# Copyright (C) 2018 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 re +import json + +from ctypes import cdll, c_char_p, c_void_p, c_int + + +def escape_backslash(string: str) -> str: + """Escape single backslashes in string that are not in escape sequence""" + p = re.compile(r'(?<!\\)[\\](?!b|f|n|r|t|\\[^bfnrt])') + result = p.sub(r'\\\\', string) + return result + +def extract_version(s): + """ Extract the version string from the config string """ + t = re.split('(^//)', s, maxsplit=1, flags=re.MULTILINE) + return (s, ''.join(t[1:])) + +def check_path(path): + # Necessary type checking + if not isinstance(path, list): + raise TypeError("Expected a list, got a {}".format(type(path))) + else: + pass + + +class ConfigTreeError(Exception): + pass + + +class ConfigTree(object): + def __init__(self, config_string, libpath='/usr/lib/libvyosconfig.so.0'): + self.__config = None + self.__lib = cdll.LoadLibrary(libpath) + + # Import functions + self.__from_string = self.__lib.from_string + self.__from_string.argtypes = [c_char_p] + self.__from_string.restype = c_void_p + + self.__get_error = self.__lib.get_error + self.__get_error.argtypes = [] + self.__get_error.restype = c_char_p + + self.__to_string = self.__lib.to_string + self.__to_string.argtypes = [c_void_p] + self.__to_string.restype = c_char_p + + self.__to_commands = self.__lib.to_commands + self.__to_commands.argtypes = [c_void_p] + self.__to_commands.restype = c_char_p + + self.__to_json = self.__lib.to_json + self.__to_json.argtypes = [c_void_p] + self.__to_json.restype = c_char_p + + self.__to_json_ast = self.__lib.to_json_ast + self.__to_json_ast.argtypes = [c_void_p] + self.__to_json_ast.restype = c_char_p + + self.__set_add_value = self.__lib.set_add_value + self.__set_add_value.argtypes = [c_void_p, c_char_p, c_char_p] + self.__set_add_value.restype = c_int + + self.__delete_value = self.__lib.delete_value + self.__delete_value.argtypes = [c_void_p, c_char_p, c_char_p] + self.__delete_value.restype = c_int + + self.__delete = self.__lib.delete_node + self.__delete.argtypes = [c_void_p, c_char_p] + self.__delete.restype = c_int + + self.__rename = self.__lib.rename_node + self.__rename.argtypes = [c_void_p, c_char_p, c_char_p] + self.__rename.restype = c_int + + self.__copy = self.__lib.copy_node + self.__copy.argtypes = [c_void_p, c_char_p, c_char_p] + self.__copy.restype = c_int + + self.__set_replace_value = self.__lib.set_replace_value + self.__set_replace_value.argtypes = [c_void_p, c_char_p, c_char_p] + self.__set_replace_value.restype = c_int + + self.__set_valueless = self.__lib.set_valueless + self.__set_valueless.argtypes = [c_void_p, c_char_p] + self.__set_valueless.restype = c_int + + self.__exists = self.__lib.exists + self.__exists.argtypes = [c_void_p, c_char_p] + self.__exists.restype = c_int + + self.__list_nodes = self.__lib.list_nodes + self.__list_nodes.argtypes = [c_void_p, c_char_p] + self.__list_nodes.restype = c_char_p + + self.__return_value = self.__lib.return_value + self.__return_value.argtypes = [c_void_p, c_char_p] + self.__return_value.restype = c_char_p + + self.__return_values = self.__lib.return_values + self.__return_values.argtypes = [c_void_p, c_char_p] + self.__return_values.restype = c_char_p + + self.__is_tag = self.__lib.is_tag + self.__is_tag.argtypes = [c_void_p, c_char_p] + self.__is_tag.restype = c_int + + self.__set_tag = self.__lib.set_tag + self.__set_tag.argtypes = [c_void_p, c_char_p] + self.__set_tag.restype = c_int + + self.__destroy = self.__lib.destroy + self.__destroy.argtypes = [c_void_p] + + config_section, version_section = extract_version(config_string) + config_section = escape_backslash(config_section) + config = self.__from_string(config_section.encode()) + if config is None: + msg = self.__get_error().decode() + raise ValueError("Failed to parse config: {0}".format(msg)) + else: + self.__config = config + self.__version = version_section + + def __del__(self): + if self.__config is not None: + self.__destroy(self.__config) + + def __str__(self): + return self.to_string() + + def to_string(self): + config_string = self.__to_string(self.__config).decode() + config_string = "{0}\n{1}".format(config_string, self.__version) + return config_string + + def to_commands(self): + return self.__to_commands(self.__config).decode() + + def to_json(self): + return self.__to_json(self.__config).decode() + + def to_json_ast(self): + return self.__to_json_ast(self.__config).decode() + + def set(self, path, value=None, replace=True): + """Set new entry in VyOS configuration. + path: configuration path e.g. 'system dns forwarding listen-address' + value: value to be added to node, e.g. '172.18.254.201' + replace: True: current occurance will be replaced + False: new value will be appended to current occurances - use + this for adding values to a multi node + """ + + check_path(path) + path_str = " ".join(map(str, path)).encode() + + if value is None: + self.__set_valueless(self.__config, path_str) + else: + if replace: + self.__set_replace_value(self.__config, path_str, str(value).encode()) + else: + self.__set_add_value(self.__config, path_str, str(value).encode()) + + def delete(self, path): + check_path(path) + path_str = " ".join(map(str, path)).encode() + + self.__delete(self.__config, path_str) + + def delete_value(self, path, value): + check_path(path) + path_str = " ".join(map(str, path)).encode() + + self.__delete_value(self.__config, path_str, value.encode()) + + def rename(self, path, new_name): + check_path(path) + path_str = " ".join(map(str, path)).encode() + newname_str = new_name.encode() + + # Check if a node with intended new name already exists + new_path = path[:-1] + [new_name] + if self.exists(new_path): + raise ConfigTreeError() + res = self.__rename(self.__config, path_str, newname_str) + if (res != 0): + raise ConfigTreeError("Path [{}] doesn't exist".format(path)) + + def copy(self, old_path, new_path): + check_path(old_path) + check_path(new_path) + oldpath_str = " ".join(map(str, old_path)).encode() + newpath_str = " ".join(map(str, new_path)).encode() + + # Check if a node with intended new name already exists + if self.exists(new_path): + raise ConfigTreeError() + res = self.__copy(self.__config, oldpath_str, newpath_str) + if (res != 0): + raise ConfigTreeError("Path [{}] doesn't exist".format(old_path)) + + def exists(self, path): + check_path(path) + path_str = " ".join(map(str, path)).encode() + + res = self.__exists(self.__config, path_str) + if (res == 0): + return False + else: + return True + + def list_nodes(self, path): + check_path(path) + path_str = " ".join(map(str, path)).encode() + + res_json = self.__list_nodes(self.__config, path_str).decode() + res = json.loads(res_json) + + if res is None: + raise ConfigTreeError("Path [{}] doesn't exist".format(path_str)) + else: + return res + + def return_value(self, path): + check_path(path) + path_str = " ".join(map(str, path)).encode() + + res_json = self.__return_value(self.__config, path_str).decode() + res = json.loads(res_json) + + if res is None: + raise ConfigTreeError("Path [{}] doesn't exist".format(path_str)) + else: + return res + + def return_values(self, path): + check_path(path) + path_str = " ".join(map(str, path)).encode() + + res_json = self.__return_values(self.__config, path_str).decode() + res = json.loads(res_json) + + if res is None: + raise ConfigTreeError("Path [{}] doesn't exist".format(path_str)) + else: + return res + + def is_tag(self, path): + check_path(path) + path_str = " ".join(map(str, path)).encode() + + res = self.__is_tag(self.__config, path_str) + if (res >= 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)) + |