diff --git a/python/vyos/base.py b/python/vyos/base.py index 054b1d837..ca96d96ce 100644 --- a/python/vyos/base.py +++ b/python/vyos/base.py @@ -1,65 +1,72 @@ # Copyright 2018-2022 VyOS maintainers and contributors <maintainers@vyos.io> # # 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 <http://www.gnu.org/licenses/>. from textwrap import fill class BaseWarning: def __init__(self, header, message, **kwargs): self.message = message self.kwargs = kwargs if 'width' not in kwargs: self.width = 72 if 'initial_indent' in kwargs: del self.kwargs['initial_indent'] if 'subsequent_indent' in kwargs: del self.kwargs['subsequent_indent'] self.textinitindent = header self.standardindent = '' def print(self): messages = self.message.split('\n') isfirstmessage = True initial_indent = self.textinitindent print('') for mes in messages: mes = fill(mes, initial_indent=initial_indent, subsequent_indent=self.standardindent, **self.kwargs) if isfirstmessage: isfirstmessage = False initial_indent = self.standardindent print(f'{mes}') print('', flush=True) class Warning(): def __init__(self, message, **kwargs): self.BaseWarn = BaseWarning('WARNING: ', message, **kwargs) self.BaseWarn.print() class DeprecationWarning(): def __init__(self, message, **kwargs): # Reformat the message and trim it to 72 characters in length self.BaseWarn = BaseWarning('DEPRECATION WARNING: ', message, **kwargs) self.BaseWarn.print() class ConfigError(Exception): def __init__(self, message): # Reformat the message and trim it to 72 characters in length message = fill(message, width=72) # Call the base class constructor with the parameters it needs super().__init__(message) + +class MigrationError(Exception): + def __init__(self, message): + # Reformat the message and trim it to 72 characters in length + message = fill(message, width=72) + # Call the base class constructor with the parameters it needs + super().__init__(message) diff --git a/python/vyos/component_version.py b/python/vyos/component_version.py index 9662ebfcf..94215531d 100644 --- a/python/vyos/component_version.py +++ b/python/vyos/component_version.py @@ -1,167 +1,204 @@ -# Copyright 2022 VyOS maintainers and contributors <maintainers@vyos.io> +# Copyright 2022-2024 VyOS maintainers and contributors <maintainers@vyos.io> # # 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 <http://www.gnu.org/licenses/>. """ Functions for reading/writing component versions. The config file version string has the following form: VyOS 1.3/1.4: // Warning: Do not remove the following line. // vyos-config-version: "broadcast-relay@1:cluster@1:config-management@1:conntrack@3:conntrack-sync@2:dhcp-relay@2:dhcp-server@6:dhcpv6-server@1:dns-forwarding@3:firewall@5:https@2:interfaces@22:ipoe-server@1:ipsec@5:isis@1:l2tp@3:lldp@1:mdns@1:nat@5:ntp@1:pppoe-server@5:pptp@2:qos@1:quagga@8:rpki@1:salt@1:snmp@2:ssh@2:sstp@3:system@21:vrrp@2:vyos-accel-ppp@2:wanloadbalance@3:webproxy@2:zone-policy@1" // Release version: 1.3.0 VyOS 1.2: /* Warning: Do not remove the following line. */ /* === vyatta-config-version: "broadcast-relay@1:cluster@1:config-management@1:conntrack-sync@1:conntrack@1:dhcp-relay@2:dhcp-server@5:dns-forwarding@1:firewall@5:ipsec@5:l2tp@1:mdns@1:nat@4:ntp@1:pppoe-server@2:pptp@1:qos@1:quagga@7:snmp@1:ssh@1:system@10:vrrp@2:wanloadbalance@3:webgui@1:webproxy@2:zone-policy@1" === */ /* Release version: 1.2.8 */ """ import os import re import sys -import fileinput +from dataclasses import dataclass +from dataclasses import replace +from typing import Optional from vyos.xml_ref import component_version +from vyos.utils.file import write_file from vyos.version import get_version from vyos.defaults import directories DEFAULT_CONFIG_PATH = os.path.join(directories['config'], 'config.boot') -def from_string(string_line, vintage='vyos'): - """ - Get component version dictionary from string. - Return empty dictionary if string contains no config information - or raise error if component version string malformed. - """ - version_dict = {} - - if vintage == 'vyos': - if re.match(r'// vyos-config-version:.+', string_line): - if not re.match(r'// vyos-config-version:\s+"([\w,-]+@\d+:)+([\w,-]+@\d+)"\s*', string_line): - raise ValueError(f"malformed configuration string: {string_line}") +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_RELEASE_VERSION_VYOS = r'// Release version:\s+(\S*)\s*' +REGEX_RELEASE_VERSION_VYATTA = r'/\* Release version:\s+(\S*)\s*\*/' - for pair in re.findall(r'([\w,-]+)@(\d+)', string_line): - version_dict[pair[0]] = int(pair[1]) - - elif vintage == 'vyatta': - if re.match(r'/\* === vyatta-config-version:.+=== \*/$', string_line): - if not re.match(r'/\* === vyatta-config-version:\s+"([\w,-]+@\d+:)+([\w,-]+@\d+)"\s+=== \*/$', string_line): - raise ValueError(f"malformed configuration string: {string_line}") +CONFIG_FILE_VERSION = """\ +// Warning: Do not remove the following line. +// vyos-config-version: "{}" +// Release version: {} +""" - for pair in re.findall(r'([\w,-]+)@(\d+)', string_line): - version_dict[pair[0]] = int(pair[1]) +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)])) } + +@dataclass +class VersionInfo: + component: Optional[dict[str,int]] = None + release: str = get_version() + vintage: str = 'vyos' + config_body: Optional[str] = None + footer_lines: Optional[list[str]] = None + + def component_is_none(self) -> bool: + return bool(self.component is None) + + def config_body_is_none(self) -> bool: + return bool(self.config_body is None) + + def update_footer(self): + f = CONFIG_FILE_VERSION.format(component_to_string(self.component), + self.release) + self.footer_lines = f.splitlines() + + def update_syntax(self): + self.vintage = 'vyos' + self.update_footer() + + def update_release(self, release: str): + self.release = release + self.update_footer() + + def update_component(self, key: str, version: int): + if not isinstance(version, int): + raise ValueError('version must be int') + if self.component is None: + self.component = {} + self.component[key] = version + self.component = dict(sorted(self.component.items(), key=lambda x: x[0])) + self.update_footer() + + def update_config_body(self, config_str: str): + self.config_body = config_str + + def write_string(self) -> str: + config_body = '' if self.config_body is None else self.config_body + footer_lines = [] if self.footer_lines is None else self.footer_lines + + return config_body + '\n' + '\n'.join(footer_lines) + '\n' + + def write(self, config_file): + string = self.write_string() + try: + write_file(config_file, string) + 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])] + 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() + try: + with open(config_file) as f: + config_str = f.read() + except OSError: + return None + + if len(parts := warn_filter_vyos.split(config_str)) > 1: + vintage = 'vyos' + elif len(parts := warn_filter_vyatta.split(config_str)) > 1: + vintage = 'vyatta' else: - raise ValueError("Unknown config string vintage") - - return version_dict - -def from_file(config_file_name=DEFAULT_CONFIG_PATH, vintage='vyos'): - """ - Get component version dictionary parsing config file line by line - """ - with open(config_file_name, 'r') as f: - for line_in_config in f: - version_dict = from_string(line_in_config, vintage=vintage) - if version_dict: - return version_dict - - # no version information - return {} - -def from_system(): - """ - Get system component version dict. - """ - return component_version() - -def format_string(ver: dict) -> str: - """ - Version dict to string. - """ - keys = list(ver) - keys.sort() - l = [] - for k in keys: - v = ver[k] - l.append(f'{k}@{v}') - sep = ':' - return sep.join(l) - -def version_footer(ver: dict, vintage='vyos') -> str: - """ - Version footer as string. - """ - ver_str = format_string(ver) - release = get_version() - if vintage == 'vyos': - ret_str = (f'// Warning: Do not remove the following line.\n' - + f'// vyos-config-version: "{ver_str}"\n' - + f'// Release version: {release}\n') - elif vintage == 'vyatta': - ret_str = (f'/* Warning: Do not remove the following line. */\n' - + f'/* === vyatta-config-version: "{ver_str}" === */\n' - + f'/* Release version: {release} */\n') - else: - raise ValueError("Unknown config string vintage") - - return ret_str - -def system_footer(vintage='vyos') -> str: - """ - System version footer as string. - """ - ver_d = from_system() - return version_footer(ver_d, vintage=vintage) - -def write_version_footer(ver: dict, file_name, vintage='vyos'): - """ - Write version footer to file. - """ - footer = version_footer(ver=ver, vintage=vintage) - if file_name: - with open(file_name, 'a') as f: - f.write(footer) + version_info.config_body = parts[0] if parts else None + return version_info + + version_info.vintage = vintage + version_info.config_body = parts[0] + version_lines = ''.join(parts[1:]).splitlines() + version_lines = [k for k in version_lines if k] + if len(version_lines) != 3: + raise ValueError(f'Malformed version strings: {version_lines}') + + m = regex_filter[vintage]['component'].match(version_lines[1]) + if not m: + raise ValueError(f'Malformed component string: {version_lines[1]}') + version_info.component = component_from_string(m.group(1)) + + m = regex_filter[vintage]['release'].match(version_lines[2]) + if not m: + raise ValueError(f'Malformed component string: {version_lines[2]}') + version_info.release = m.group(1) + + version_info.footer_lines = version_lines + + 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' + ) + + 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 } + +def add_system_version(config_str: str = None, out_file: str = None): + """Wrap config string with system version and write to out_file. + + For convenience, calling with no argument will write system version + string to stdout, for use in bash scripts. + """ + version_info = version_info_from_system() + if config_str is not None: + version_info.update_config_body(config_str) + version_info.update_footer() + if out_file is not None: + version_info.write(out_file) else: - sys.stdout.write(footer) - -def write_system_footer(file_name, vintage='vyos'): - """ - Write system version footer to file. - """ - ver_d = from_system() - return write_version_footer(ver_d, file_name=file_name, vintage=vintage) - -def remove_footer(file_name): - """ - Remove old version footer. - """ - for line in fileinput.input(file_name, inplace=True): - if re.match(r'/\* Warning:.+ \*/$', line): - continue - if re.match(r'/\* === vyatta-config-version:.+=== \*/$', line): - continue - if re.match(r'/\* Release version:.+ \*/$', line): - continue - if re.match('// vyos-config-version:.+', line): - continue - if re.match('// Warning:.+', line): - continue - if re.match('// Release version:.+', line): - continue - sys.stdout.write(line) + sys.stdout.write(version_info.write_string()) diff --git a/python/vyos/compose_config.py b/python/vyos/compose_config.py index efa28babe..79a8718c5 100644 --- a/python/vyos/compose_config.py +++ b/python/vyos/compose_config.py @@ -1,84 +1,88 @@ # Copyright 2024 VyOS maintainers and contributors <maintainers@vyos.io> # # 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 <http://www.gnu.org/licenses/>. """This module allows iterating over function calls to modify an existing config. """ +import traceback from pathlib import Path from typing import TypeAlias, Union, Callable from vyos.configtree import ConfigTree from vyos.configtree import deep_copy as ct_deep_copy -from vyos.utils.system import load_as_module +from vyos.utils.system import load_as_module_source ConfigObj: TypeAlias = Union[str, ConfigTree] class ComposeConfigError(Exception): """Raised when an error occurs modifying a config object. """ class ComposeConfig: """Apply function to config tree: for iteration over functions or files. """ def __init__(self, config_obj: ConfigObj, checkpoint_file=None): if isinstance(config_obj, ConfigTree): self.config_tree = config_obj else: self.config_tree = ConfigTree(config_obj) self.checkpoint = self.config_tree self.checkpoint_file = checkpoint_file def apply_func(self, func: Callable): """Apply the function to the config tree. """ if not callable(func): raise ComposeConfigError(f'{func.__name__} is not callable') if self.checkpoint_file is not None: self.checkpoint = ct_deep_copy(self.config_tree) try: func(self.config_tree) except Exception as e: - self.config_tree = self.checkpoint + if self.checkpoint_file is not None: + self.config_tree = self.checkpoint raise ComposeConfigError(e) from e def apply_file(self, func_file: str, func_name: str): """Apply named function from file. """ try: mod_name = Path(func_file).stem.replace('-', '_') - mod = load_as_module(mod_name, func_file) + mod = load_as_module_source(mod_name, func_file) func = getattr(mod, func_name) except Exception as e: raise ComposeConfigError(f'Error with {func_file}: {e}') from e try: self.apply_func(func) except ComposeConfigError as e: - raise ComposeConfigError(f'Error in {func_file}: {e}') from e + msg = str(e) + tb = f'{traceback.format_exc()}' + raise ComposeConfigError(f'Error in {func_file}: {msg}\n{tb}') from e def to_string(self, with_version=False) -> str: """Return the rendered config tree. """ return self.config_tree.to_string(no_version=not with_version) def write(self, config_file: str, with_version=False): """Write the config tree to a file. """ config_str = self.to_string(with_version=with_version) Path(config_file).write_text(config_str) diff --git a/python/vyos/configtree.py b/python/vyos/configtree.py index afd6e030b..5775070e2 100644 --- a/python/vyos/configtree.py +++ b/python/vyos/configtree.py @@ -1,492 +1,495 @@ # 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 +import logging from ctypes import cdll, c_char_p, c_void_p, c_int, c_bool LIBPATH = '/usr/lib/libvyosconfig.so.0' def replace_backslash(s, search, replace): """Modify quoted strings containing backslashes not of escape sequences""" def replace_method(match): result = match.group().replace(search, replace) return result p = re.compile(r'("[^"]*[\\][^"]*"\n|\'[^\']*[\\][^\']*\'\n)') return p.sub(replace_method, s) def escape_backslash(string: str) -> str: """Escape single backslashes in quoted strings""" result = replace_backslash(string, '\\', '\\\\') return result def unescape_backslash(string: str) -> str: """Unescape backslashes in quoted strings""" result = replace_backslash(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=None, address=None, libpath=LIBPATH): if config_string is None and address is None: raise TypeError("ConfigTree() requires one of 'config_string' or 'address'") 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, c_bool] self.__to_string.restype = c_char_p self.__to_commands = self.__lib.to_commands self.__to_commands.argtypes = [c_void_p, c_char_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.__get_subtree = self.__lib.get_subtree self.__get_subtree.argtypes = [c_void_p, c_char_p] self.__get_subtree.restype = c_void_p self.__destroy = self.__lib.destroy self.__destroy.argtypes = [c_void_p] if address is None: 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 else: self.__config = address self.__version = '' self.__migration = os.environ.get('VYOS_MIGRATION') + if self.__migration: + self.migration_log = logging.getLogger('vyos.migrate') def __del__(self): if self.__config is not None: self.__destroy(self.__config) def __str__(self): return self.to_string() def _get_config(self): return self.__config def get_version_string(self): return self.__version def to_string(self, ordered_values=False, no_version=False): config_string = self.__to_string(self.__config, ordered_values).decode() config_string = unescape_backslash(config_string) if no_version: return config_string config_string = "{0}\n{1}".format(config_string, self.__version) return config_string def to_commands(self, op="set"): commands = self.__to_commands(self.__config, op.encode()).decode() commands = unescape_backslash(commands) return commands 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()) if self.__migration: - print(f"- op: set path: {path} value: {value} replace: {replace}") + self.migration_log.info(f"- op: set path: {path} value: {value} replace: {replace}") def delete(self, path): check_path(path) path_str = " ".join(map(str, path)).encode() res = self.__delete(self.__config, path_str) if (res != 0): raise ConfigTreeError(f"Path doesn't exist: {path}") if self.__migration: - print(f"- op: delete path: {path}") + self.migration_log.info(f"- op: delete path: {path}") def delete_value(self, path, value): check_path(path) path_str = " ".join(map(str, path)).encode() res = self.__delete_value(self.__config, path_str, value.encode()) if (res != 0): if res == 1: raise ConfigTreeError(f"Path doesn't exist: {path}") elif res == 2: raise ConfigTreeError(f"Value doesn't exist: '{value}'") else: raise ConfigTreeError() if self.__migration: - print(f"- op: delete_value path: {path} value: {value}") + self.migration_log.info(f"- op: delete_value path: {path} value: {value}") 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)) if self.__migration: - print(f"- op: rename old_path: {path} new_path: {new_path}") + self.migration_log.info(f"- op: rename old_path: {path} new_path: {new_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): msg = self.__get_error().decode() raise ConfigTreeError(msg) if self.__migration: - print(f"- op: copy old_path: {old_path} new_path: {new_path}") + self.migration_log.info(f"- op: copy old_path: {old_path} new_path: {new_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)) 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) res = unescape_backslash(res) return res def union(left, right, 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") __lib = cdll.LoadLibrary(libpath) __tree_union = __lib.tree_union __tree_union.argtypes = [c_void_p, c_void_p] __tree_union.restype = c_void_p __get_error = __lib.get_error __get_error.argtypes = [] __get_error.restype = c_char_p res = __tree_union( left._get_config(), right._get_config()) tree = ConfigTree(address=res) return tree def mask_inclusive(left, right, libpath=LIBPATH): if not (isinstance(left, ConfigTree) and isinstance(right, ConfigTree)): raise TypeError("Arguments must be instances of ConfigTree") try: __lib = cdll.LoadLibrary(libpath) __mask_tree = __lib.mask_tree __mask_tree.argtypes = [c_void_p, c_void_p] __mask_tree.restype = c_void_p __get_error = __lib.get_error __get_error.argtypes = [] __get_error.restype = c_char_p res = __mask_tree(left._get_config(), right._get_config()) except Exception as e: raise ConfigTreeError(e) if not res: msg = __get_error().decode() raise ConfigTreeError(msg) tree = ConfigTree(address=res) return tree def reference_tree_to_json(from_dir, to_file, libpath=LIBPATH): try: __lib = cdll.LoadLibrary(libpath) __reference_tree_to_json = __lib.reference_tree_to_json __reference_tree_to_json.argtypes = [c_char_p, c_char_p] __get_error = __lib.get_error __get_error.argtypes = [] __get_error.restype = c_char_p res = __reference_tree_to_json(from_dir.encode(), to_file.encode()) except Exception as e: raise ConfigTreeError(e) if res == 1: msg = __get_error().decode() raise ConfigTreeError(msg) 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 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']) self.delete = self.full.get_subtree(['del']) def to_commands(self): add = self.add.to_commands() delete = self.delete.to_commands(op="delete") return delete + "\n" + add def deep_copy(config_tree: ConfigTree) -> ConfigTree: """An inelegant, but reasonably fast, copy; replace with backend copy """ D = DiffTree(None, config_tree) return D.add diff --git a/python/vyos/load_config.py b/python/vyos/load_config.py index af563614d..b910a2f92 100644 --- a/python/vyos/load_config.py +++ b/python/vyos/load_config.py @@ -1,200 +1,181 @@ -# Copyright 2023 VyOS maintainers and contributors <maintainers@vyos.io> +# Copyright 2023-2024 VyOS maintainers and contributors <maintainers@vyos.io> # # 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 <http://www.gnu.org/licenses/>. """This module abstracts the loading of a config file into the running config. It provides several varieties of loading a config file, from the legacy version to the developing versions, as a means of offering alternatives for competing use cases, and a base for profiling the performance of each. """ import sys from pathlib import Path from tempfile import NamedTemporaryFile from typing import Union, Literal, TypeAlias, get_type_hints, get_args from vyos.config import Config from vyos.configtree import ConfigTree, DiffTree from vyos.configsource import ConfigSourceSession, VyOSError -from vyos.component_version import from_string as version_from_string -from vyos.component_version import from_system as version_from_system -from vyos.migrator import Migrator, VirtualMigrator, MigratorError +from vyos.migrate import ConfigMigrate, ConfigMigrateError from vyos.utils.process import popen, DEVNULL Variety: TypeAlias = Literal['explicit', 'batch', 'tree', 'legacy'] ConfigObj: TypeAlias = Union[str, ConfigTree] thismod = sys.modules[__name__] class LoadConfigError(Exception): """Raised when an error occurs loading a config file. """ # utility functions def get_running_config(config: Config) -> ConfigTree: return config.get_config_tree(effective=True) def get_proposed_config(config_file: str = None) -> ConfigTree: config_str = Path(config_file).read_text() return ConfigTree(config_str) -def migration_needed(config_obj: ConfigObj) -> bool: - """Check if a migration is needed for the config object. - """ - if not isinstance(config_obj, ConfigTree): - atree = get_proposed_config(config_obj) - else: - atree = config_obj - version_str = atree.get_version_string() - if not version_str: - return True - aversion = version_from_string(version_str.splitlines()[1]) - bversion = version_from_system() - return aversion != bversion - def check_session(strict: bool, switch: Variety) -> None: """Check if we are in a config session, with no uncommitted changes, if strict. This is not needed for legacy load, as these checks are implicit. """ if switch == 'legacy': return context = ConfigSourceSession() if not context.in_session(): raise LoadConfigError('not in a config session') if strict and context.session_changed(): raise LoadConfigError('commit or discard changes before loading config') # methods to call for each variety # explicit def diff_to_commands(ctree: ConfigTree, ntree: ConfigTree) -> list: """Calculate the diff between the current and proposed config.""" # Calculate the diff between the current and new config tree commands = DiffTree(ctree, ntree).to_commands() # on an empty set of 'add' or 'delete' commands, to_commands # returns '\n'; prune below command_list = commands.splitlines() command_list = [c for c in command_list if c] return command_list def set_commands(cmds: list) -> None: """Set commands in the config session.""" if not cmds: print('no commands to set') return error_out = [] for op in cmds: out, rc = popen(f'/opt/vyatta/sbin/my_{op}', shell=True, stderr=DEVNULL) if rc != 0: error_out.append(out) continue if error_out: out = '\n'.join(error_out) raise LoadConfigError(out) # legacy class LoadConfig(ConfigSourceSession): """A subclass for calling 'loadFile'. """ def load_config(self, file_name): return self._run(['/bin/cli-shell-api','loadFile', file_name]) # end methods to call for each variety def migrate(config_obj: ConfigObj) -> ConfigObj: """Migrate a config object to the current version. """ if isinstance(config_obj, ConfigTree): config_file = NamedTemporaryFile(delete=False).name Path(config_file).write_text(config_obj.to_string()) else: config_file = config_obj - virtual_migration = VirtualMigrator(config_file) - migration = Migrator(config_file) + config_migrate = ConfigMigrate(config_file) try: - virtual_migration.run() - migration.run() - except MigratorError as e: + config_migrate.run() + except ConfigMigrateError as e: raise LoadConfigError(e) from e else: if isinstance(config_obj, ConfigTree): return ConfigTree(Path(config_file).read_text()) return config_file finally: if isinstance(config_obj, ConfigTree): Path(config_file).unlink() def load_explicit(config_obj: ConfigObj): """Explicit load from file or configtree. """ config = Config() ctree = get_running_config(config) if isinstance(config_obj, ConfigTree): ntree = config_obj else: ntree = get_proposed_config(config_obj) # Calculate the diff between the current and proposed config cmds = diff_to_commands(ctree, ntree) # Set the commands in the config session set_commands(cmds) def load_batch(config_obj: ConfigObj): # requires legacy backend patch raise NotImplementedError('batch loading not implemented') def load_tree(config_obj: ConfigObj): # requires vyconf backend patch raise NotImplementedError('tree loading not implemented') def load_legacy(config_obj: ConfigObj): """Legacy load from file or configtree. """ if isinstance(config_obj, ConfigTree): config_file = NamedTemporaryFile(delete=False).name Path(config_file).write_text(config_obj.to_string()) else: config_file = config_obj config = LoadConfig() try: config.load_config(config_file) except VyOSError as e: raise LoadConfigError(e) from e finally: if isinstance(config_obj, ConfigTree): Path(config_file).unlink() def load(config_obj: ConfigObj, strict: bool = True, switch: Variety = 'legacy'): type_hints = get_type_hints(load) switch_choice = get_args(type_hints['switch']) if switch not in switch_choice: raise ValueError(f'invalid switch: {switch}') check_session(strict, switch) - if migration_needed(config_obj): - config_obj = migrate(config_obj) + config_obj = migrate(config_obj) func = getattr(thismod, f'load_{switch}') func(config_obj) diff --git a/python/vyos/migrate.py b/python/vyos/migrate.py new file mode 100644 index 000000000..9d1613676 --- /dev/null +++ b/python/vyos/migrate.py @@ -0,0 +1,283 @@ +# Copyright 2019-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# 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 <http://www.gnu.org/licenses/>. + +import os +import re +import json +import logging +from pathlib import Path +from grp import getgrnam + +from vyos.component_version import VersionInfo +from vyos.component_version import version_info_from_system +from vyos.component_version import version_info_from_file +from vyos.component_version import version_info_copy +from vyos.component_version import version_info_prune_component +from vyos.compose_config import ComposeConfig +from vyos.compose_config import ComposeConfigError +from vyos.configtree import ConfigTree +from vyos.defaults import directories as default_dir +from vyos.defaults import component_version_json + + +log_file = Path(default_dir['config']).joinpath('vyos-migrate.log') + +class ConfigMigrateError(Exception): + """Raised on error in config migration.""" + +class ConfigMigrate: + # pylint: disable=too-many-instance-attributes + # the number is reasonable in this case + def __init__(self, config_file: str, force=False, + output_file: str = None, checkpoint_file: str = None): + self.config_file: str = config_file + self.force: bool = force + self.system_version: VersionInfo = version_info_from_system() + self.file_version: VersionInfo = version_info_from_file(self.config_file) + self.compose = None + self.output_file = output_file + self.checkpoint_file = checkpoint_file + self.logger = None + self.config_modified = True + + if self.file_version is None: + raise ConfigMigrateError(f'failed to read config file {self.config_file}') + + def migration_needed(self) -> bool: + return self.system_version.component != self.file_version.component + + def release_update_needed(self) -> bool: + return self.system_version.release != self.file_version.release + + def syntax_update_needed(self) -> bool: + return self.system_version.vintage != self.file_version.vintage + + def update_release(self): + """ + Update config file release version. + """ + self.file_version.update_release(self.system_version.release) + + def update_syntax(self): + """ + Update config file syntax. + """ + self.file_version.update_syntax() + + @staticmethod + def normalize_config_body(version_info: VersionInfo): + """ + This is an interim workaround for the cosmetic issue of node + ordering when composing operations on the internal config_tree: + ordering is performed on parsing, hence was maintained in the old + system which would parse/write on each application of a migration + script (~200). Here, we will take the cost of one extra parsing to + reorder before save, for easier review. + """ + if not version_info.config_body_is_none(): + ct = ConfigTree(version_info.config_body) + version_info.update_config_body(ct.to_string()) + + def write_config(self): + if self.output_file is not None: + config_file = self.output_file + else: + config_file = self.config_file + + try: + self.file_version.write(config_file) + except ValueError as e: + raise ConfigMigrateError(f'failed to write {config_file}: {e}') from e + + def init_logger(self): + self.logger = logging.getLogger(__name__) + self.logger.setLevel(logging.DEBUG) + + fh = ConfigMigrate.group_perm_file_handler(log_file, + group='vyattacfg', + mode='w') + fh.setLevel(logging.INFO) + fh_formatter = logging.Formatter('%(message)s') + fh.setFormatter(fh_formatter) + self.logger.addHandler(fh) + ch = logging.StreamHandler() + ch.setLevel(logging.WARNING) + ch_formatter = logging.Formatter('%(name)s - %(levelname)s - %(message)s') + ch.setFormatter(ch_formatter) + self.logger.addHandler(ch) + + @staticmethod + def group_perm_file_handler(filename, group=None, mode='a'): + # pylint: disable=consider-using-with + if group is None: + return logging.FileHandler(filename, mode) + gid = getgrnam(group).gr_gid + if not os.path.exists(filename): + open(filename, 'a').close() + os.chown(filename, -1, gid) + os.chmod(filename, 0o664) + return logging.FileHandler(filename, mode) + + @staticmethod + def sort_function(): + """ + Define sort function for migration files as tuples (n, m) for file + n-to-m. + """ + numbers = re.compile(r'(\d+)') + def func(p: Path): + parts = numbers.split(p.stem) + return list(map(int, parts[1::2])) + return func + + @staticmethod + def file_ext(file_path: Path) -> str: + """ + Return an identifier from file name for checkpoint file extension. + """ + return f'{file_path.parent.stem}_{file_path.stem}' + + def run_migration_scripts(self): + """ + Call migration files iteratively. + """ + os.environ['VYOS_MIGRATION'] = '1' + + self.init_logger() + self.logger.info("List of applied migration modules:") + + components = list(self.system_version.component) + components.sort() + + # T4382: 'bgp' needs to follow 'quagga': + if 'bgp' in components and 'quagga' in components: + components.insert(components.index('quagga'), + components.pop(components.index('bgp'))) + + revision: VersionInfo = version_info_copy(self.file_version) + # prune retired, for example, zone-policy + version_info_prune_component(revision, self.system_version) + + migrate_dir = Path(default_dir['migrate']) + sort_func = ConfigMigrate.sort_function() + + for key in components: + p = migrate_dir.joinpath(key) + script_list = list(p.glob('*-to-*')) + script_list = sorted(script_list, key=sort_func) + + if not self.file_version.component_is_none() and not self.force: + start = self.file_version.component.get(key, 0) + script_list = list(filter(lambda x, st=start: sort_func(x)[0] >= st, + script_list)) + + if not script_list: # no applicable migration scripts + revision.update_component(key, self.system_version.component[key]) + continue + + for file in script_list: + f = file.as_posix() + self.logger.info(f'applying {f}') + try: + self.compose.apply_file(f, func_name='migrate') + except ComposeConfigError as e: + self.logger.error(e) + if self.checkpoint_file: + check = f'{self.checkpoint_file}_{ConfigMigrate.file_ext(file)}' + revision.update_config_body(self.compose.to_string()) + ConfigMigrate.normalize_config_body(revision) + revision.write(check) + break + else: + revision.update_component(key, sort_func(file)[1]) + + revision.update_config_body(self.compose.to_string()) + ConfigMigrate.normalize_config_body(revision) + self.file_version = version_info_copy(revision) + + if revision.component != self.system_version.component: + raise ConfigMigrateError(f'incomplete migration: check {log_file} for error') + + del os.environ['VYOS_MIGRATION'] + + def save_json_record(self): + """ + Write component versions to a json file + """ + version_file = component_version_json + + try: + with open(version_file, 'w') as f: + f.write(json.dumps(self.system_version.component, + indent=2, sort_keys=True)) + except OSError: + pass + + def load_config(self): + """ + Instantiate a ComposeConfig object with the config string. + """ + + self.compose = ComposeConfig(self.file_version.config_body, self.checkpoint_file) + + def run(self): + """ + If migration needed, run migration scripts and update config file. + If only release version update needed, update release version. + """ + # save system component versions in json file for reference + self.save_json_record() + + if not self.migration_needed(): + if self.release_update_needed(): + self.update_release() + self.write_config() + else: + self.config_modified = False + return + + if self.syntax_update_needed(): + self.update_syntax() + self.write_config() + + self.load_config() + + self.run_migration_scripts() + + self.update_release() + self.write_config() + + def run_script(self, test_script: str): + """ + Run a single migration script. For testing this simply provides the + body for loading and writing the result; the component string is not + updated. + """ + + self.load_config() + self.init_logger() + + os.environ['VYOS_MIGRATION'] = '1' + + try: + self.compose.apply_file(test_script, func_name='migrate') + except ComposeConfigError as e: + self.logger.error(f'config-migration error in {test_script}: {e}') + else: + self.file_version.update_config_body(self.compose.to_string()) + + del os.environ['VYOS_MIGRATION'] + + self.write_config() diff --git a/python/vyos/migrator.py b/python/vyos/migrator.py deleted file mode 100644 index 872682bc0..000000000 --- a/python/vyos/migrator.py +++ /dev/null @@ -1,226 +0,0 @@ -# Copyright 2019-2022 VyOS maintainers and contributors <maintainers@vyos.io> -# -# 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 <http://www.gnu.org/licenses/>. - -import sys -import os -import json -import logging - -import vyos.defaults -import vyos.component_version as component_version -from vyos.utils.process import cmd - -log_file = os.path.join(vyos.defaults.directories['config'], 'vyos-migrate.log') - -class MigratorError(Exception): - pass - -class Migrator(object): - def __init__(self, config_file, force=False, set_vintage='vyos'): - self._config_file = config_file - self._force = force - self._set_vintage = set_vintage - self._config_file_vintage = None - self._changed = False - - def init_logger(self): - self.logger = logging.getLogger(__name__) - self.logger.setLevel(logging.DEBUG) - - # on adding the file handler, allow write permission for cfg_group; - # restore original umask on exit - mask = os.umask(0o113) - fh = logging.FileHandler(log_file) - formatter = logging.Formatter('%(message)s') - fh.setFormatter(formatter) - self.logger.addHandler(fh) - os.umask(mask) - - def read_config_file_versions(self): - """ - Get component versions from config file footer and set vintage; - return empty dictionary if config string is missing. - """ - cfg_file = self._config_file - component_versions = {} - - cfg_versions = component_version.from_file(cfg_file, vintage='vyatta') - - if cfg_versions: - self._config_file_vintage = 'vyatta' - component_versions = cfg_versions - - cfg_versions = component_version.from_file(cfg_file, vintage='vyos') - - if cfg_versions: - self._config_file_vintage = 'vyos' - component_versions = cfg_versions - - return component_versions - - def update_vintage(self): - old_vintage = self._config_file_vintage - - if self._set_vintage: - self._config_file_vintage = self._set_vintage - - if self._config_file_vintage not in ['vyatta', 'vyos']: - raise MigratorError("Unknown vintage.") - - if self._config_file_vintage == old_vintage: - return False - else: - return True - - def run_migration_scripts(self, config_file_versions, system_versions): - """ - Run migration scripts iteratively, until config file version equals - system component version. - """ - os.environ['VYOS_MIGRATION'] = '1' - self.init_logger() - - self.logger.info("List of executed migration scripts:") - - cfg_versions = config_file_versions - sys_versions = system_versions - - sys_keys = list(sys_versions.keys()) - sys_keys.sort() - - # XXX 'bgp' needs to follow 'quagga': - if 'bgp' in sys_keys and 'quagga' in sys_keys: - sys_keys.insert(sys_keys.index('quagga'), - sys_keys.pop(sys_keys.index('bgp'))) - - rev_versions = {} - - for key in sys_keys: - sys_ver = sys_versions[key] - if key in cfg_versions: - cfg_ver = cfg_versions[key] - else: - cfg_ver = 0 - - migrate_script_dir = os.path.join( - vyos.defaults.directories['migrate'], key) - - while cfg_ver < sys_ver: - next_ver = cfg_ver + 1 - - migrate_script = os.path.join(migrate_script_dir, - '{}-to-{}'.format(cfg_ver, next_ver)) - - try: - out = cmd([migrate_script, self._config_file]) - self.logger.info(f'{migrate_script}') - if out: self.logger.info(out) - except FileNotFoundError: - pass - except Exception as err: - print("\nMigration script error: {0}: {1}." - "".format(migrate_script, err)) - sys.exit(1) - - cfg_ver = next_ver - rev_versions[key] = cfg_ver - - del os.environ['VYOS_MIGRATION'] - return rev_versions - - def write_config_file_versions(self, cfg_versions): - """ - Write new versions string. - """ - if self._config_file_vintage == 'vyatta': - component_version.write_version_footer(cfg_versions, - self._config_file, - vintage='vyatta') - - if self._config_file_vintage == 'vyos': - component_version.write_version_footer(cfg_versions, - self._config_file, - vintage='vyos') - - def save_json_record(self, component_versions: dict): - """ - Write component versions to a json file - """ - mask = os.umask(0o113) - version_file = vyos.defaults.component_version_json - try: - with open(version_file, 'w') as f: - f.write(json.dumps(component_versions, indent=2, sort_keys=True)) - except OSError: - pass - finally: - os.umask(mask) - - def run(self): - """ - Gather component versions from config file and system. - Run migration scripts. - Update vintage ('vyatta' or 'vyos'), if needed. - If changed, remove old versions string from config file, and - write new versions string. - """ - cfg_file = self._config_file - - cfg_versions = self.read_config_file_versions() - if self._force: - # This will force calling all migration scripts: - cfg_versions = {} - - sys_versions = component_version.from_system() - - # save system component versions in json file for easy reference - self.save_json_record(sys_versions) - - rev_versions = self.run_migration_scripts(cfg_versions, sys_versions) - - if rev_versions != cfg_versions: - self._changed = True - - if self.update_vintage(): - self._changed = True - - if not self._changed: - return - - component_version.remove_footer(cfg_file) - - self.write_config_file_versions(rev_versions) - - def config_changed(self): - return self._changed - -class VirtualMigrator(Migrator): - def run(self): - cfg_file = self._config_file - - cfg_versions = self.read_config_file_versions() - if not cfg_versions: - return - - if self.update_vintage(): - self._changed = True - - if not self._changed: - return - - component_version.remove_footer(cfg_file) - - self.write_config_file_versions(cfg_versions) - diff --git a/python/vyos/utils/dict.py b/python/vyos/utils/dict.py index 9a4671c5f..1a7a6b96f 100644 --- a/python/vyos/utils/dict.py +++ b/python/vyos/utils/dict.py @@ -1,374 +1,374 @@ # Copyright 2023 VyOS maintainers and contributors <maintainers@vyos.io> # # 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 <http://www.gnu.org/licenses/>. def colon_separated_to_dict(data_string, uniquekeys=False): """ Converts a string containing newline-separated entries of colon-separated key-value pairs into a dict. Such files are common in Linux /proc filesystem Args: data_string (str): data string uniquekeys (bool): whether to insist that keys are unique or not Returns: dict Raises: ValueError: if uniquekeys=True and the data string has duplicate keys. Note: If uniquekeys=True, then dict entries are always strings, otherwise they are always lists of strings. """ import re - key_value_re = re.compile('([^:]+)\s*\:\s*(.*)') + key_value_re = re.compile(r'([^:]+)\s*\:\s*(.*)') data_raw = re.split('\n', data_string) data = {} for l in data_raw: l = l.strip() if l: match = re.match(key_value_re, l) if match and (len(match.groups()) == 2): key = match.groups()[0].strip() value = match.groups()[1].strip() else: raise ValueError(f"""Line "{l}" could not be parsed a colon-separated pair """, l) if key in data.keys(): if uniquekeys: raise ValueError("Data string has duplicate keys: {0}".format(key)) else: data[key].append(value) else: if uniquekeys: data[key] = value else: data[key] = [value] else: pass return data def mangle_dict_keys(data, regex, replacement, abs_path=None, no_tag_node_value_mangle=False): """ Mangles dict keys according to a regex and replacement character. Some libraries like Jinja2 do not like certain characters in dict keys. This function can be used for replacing all offending characters with something acceptable. Args: data (dict): Original dict to mangle regex, replacement (str): arguments to re.sub(regex, replacement, ...) abs_path (list): if data is a config dict and no_tag_node_value_mangle is True then abs_path should be the absolute config path to the first keys of data, non-inclusive no_tag_node_value_mangle (bool): do not mangle keys of tag node values Returns: dict """ import re from vyos.xml_ref import is_tag_value if abs_path is None: abs_path = [] new_dict = type(data)() for k in data.keys(): if no_tag_node_value_mangle and is_tag_value(abs_path + [k]): new_key = k else: new_key = re.sub(regex, replacement, k) value = data[k] if isinstance(value, dict): new_dict[new_key] = mangle_dict_keys(value, regex, replacement, abs_path=abs_path + [k], no_tag_node_value_mangle=no_tag_node_value_mangle) else: new_dict[new_key] = value return new_dict def _get_sub_dict(d, lpath): k = lpath[0] if k not in d.keys(): return {} c = {k: d[k]} lpath = lpath[1:] if not lpath: return c elif not isinstance(c[k], dict): return {} return _get_sub_dict(c[k], lpath) def get_sub_dict(source, lpath, get_first_key=False): """ Returns the sub-dict of a nested dict, defined by path of keys. Args: source (dict): Source dict to extract from lpath (list[str]): sequence of keys Returns: source, if lpath is empty, else {key : source[..]..[key]} for key the last element of lpath, if exists {} otherwise """ if not isinstance(source, dict): raise TypeError("source must be of type dict") if not isinstance(lpath, list): raise TypeError("path must be of type list") if not lpath: return source ret = _get_sub_dict(source, lpath) if get_first_key and lpath and ret: tmp = next(iter(ret.values())) if not isinstance(tmp, dict): raise TypeError("Data under node is not of type dict") ret = tmp return ret def dict_search(path, dict_object): """ Traverse Python dictionary (dict_object) delimited by dot (.). Return value of key if found, None otherwise. This is faster implementation then jmespath.search('foo.bar', dict_object)""" if not isinstance(dict_object, dict) or not path: return None parts = path.split('.') inside = parts[:-1] if not inside: if path not in dict_object: return None return dict_object[path] c = dict_object for p in parts[:-1]: c = c.get(p, {}) return c.get(parts[-1], None) def dict_search_args(dict_object, *path): # Traverse dictionary using variable arguments # Added due to above function not allowing for '.' in the key names # Example: dict_search_args(some_dict, 'key', 'subkey', 'subsubkey', ...) if not isinstance(dict_object, dict) or not path: return None for item in path: if item not in dict_object: return None dict_object = dict_object[item] return dict_object def dict_search_recursive(dict_object, key, path=[]): """ Traverse a dictionary recurisvely and return the value of the key we are looking for. Thankfully copied from https://stackoverflow.com/a/19871956 Modified to yield optional path to found keys """ if isinstance(dict_object, list): for i in dict_object: new_path = path + [i] for x in dict_search_recursive(i, key, new_path): yield x elif isinstance(dict_object, dict): if key in dict_object: new_path = path + [key] yield dict_object[key], new_path for k, j in dict_object.items(): new_path = path + [k] for x in dict_search_recursive(j, key, new_path): yield x def dict_set(key_path, value, dict_object): """ Set value to Python dictionary (dict_object) using path to key delimited by dot (.). The key will be added if it does not exist. """ path_list = key_path.split(".") dynamic_dict = dict_object if len(path_list) > 0: for i in range(0, len(path_list)-1): dynamic_dict = dynamic_dict[path_list[i]] dynamic_dict[path_list[len(path_list)-1]] = value def dict_delete(key_path, dict_object): """ Delete key in Python dictionary (dict_object) using path to key delimited by dot (.). """ path_dict = dict_object path_list = key_path.split('.') inside = path_list[:-1] if not inside: del dict_object[path_list] else: for key in path_list[:-1]: path_dict = path_dict[key] del path_dict[path_list[len(path_list)-1]] def dict_to_list(d, save_key_to=None): """ Convert a dict to a list of dicts. Optionally, save the original key of the dict inside dicts stores in that list. """ def save_key(i, k): if isinstance(i, dict): i[save_key_to] = k return elif isinstance(i, list): for _i in i: save_key(_i, k) else: raise ValueError(f"Cannot save the key: the item is {type(i)}, not a dict") collect = [] for k,_ in d.items(): item = d[k] if save_key_to is not None: save_key(item, k) if isinstance(item, list): collect += item else: collect.append(item) return collect def dict_to_paths_values(conf: dict) -> dict: """ Convert nested dictionary to simple dictionary, where key is a path is delimited by dot (.). """ list_of_paths = [] dict_of_options ={} for path in dict_to_key_paths(conf): str_path = '.'.join(path) list_of_paths.append(str_path) for path in list_of_paths: dict_of_options[path] = dict_search(path,conf) return dict_of_options def dict_to_key_paths(d: dict) -> list: """ Generator to return list of key paths from dict of list[str]|str """ def func(d, path): if isinstance(d, dict): if not d: yield path for k, v in d.items(): for r in func(v, path + [k]): yield r elif isinstance(d, list): yield path elif isinstance(d, str): yield path else: raise ValueError('object is not a dict of strings/list of strings') for r in func(d, []): yield r def dict_to_paths(d: dict) -> list: """ Generator to return list of paths from dict of list[str]|str """ def func(d, path): if isinstance(d, dict): if not d: yield path for k, v in d.items(): for r in func(v, path + [k]): yield r elif isinstance(d, list): for i in d: for r in func(i, path): yield r elif isinstance(d, str): yield path + [d] else: raise ValueError('object is not a dict of strings/list of strings') for r in func(d, []): yield r def embed_dict(p: list[str], d: dict) -> dict: path = p.copy() ret = d while path: ret = {path.pop(): ret} return ret def check_mutually_exclusive_options(d, keys, required=False): """ Checks if a dict has at most one or only one of mutually exclusive keys. """ present_keys = [] for k in d: if k in keys: present_keys.append(k) # Un-mangle the keys to make them match CLI option syntax from re import sub orig_keys = list(map(lambda s: sub(r'_', '-', s), keys)) orig_present_keys = list(map(lambda s: sub(r'_', '-', s), present_keys)) if len(present_keys) > 1: raise ValueError(f"Options {orig_keys} are mutually-exclusive but more than one of them is present: {orig_present_keys}") if required and (len(present_keys) < 1): raise ValueError(f"At least one of the following options is required: {orig_keys}") class FixedDict(dict): """ FixedDict: A dictionnary not allowing new keys to be created after initialisation. >>> f = FixedDict(**{'count':1}) >>> f['count'] = 2 >>> f['king'] = 3 File "...", line ..., in __setitem__ raise ConfigError(f'Option "{k}" has no defined default') """ from vyos import ConfigError def __init__(self, **options): self._allowed = options.keys() super().__init__(**options) def __setitem__(self, k, v): """ __setitem__ is a builtin which is called by python when setting dict values: >>> d = dict() >>> d['key'] = 'value' >>> d {'key': 'value'} is syntaxic sugar for >>> d = dict() >>> d.__setitem__('key','value') >>> d {'key': 'value'} """ if k not in self._allowed: raise ConfigError(f'Option "{k}" has no defined default') super().__setitem__(k, v) diff --git a/python/vyos/utils/system.py b/python/vyos/utils/system.py index 55813a5f7..cfd5b142c 100644 --- a/python/vyos/utils/system.py +++ b/python/vyos/utils/system.py @@ -1,100 +1,112 @@ # Copyright 2023 VyOS maintainers and contributors <maintainers@vyos.io> # # 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 <http://www.gnu.org/licenses/>. import os from subprocess import run def sysctl_read(name: str) -> str: """Read and return current value of sysctl() option Args: name (str): sysctl key name Returns: str: sysctl key value """ tmp = run(['sysctl', '-nb', name], capture_output=True) return tmp.stdout.decode() def sysctl_write(name: str, value: str | int) -> bool: """Change value via sysctl() Args: name (str): sysctl key name value (str | int): sysctl key value Returns: bool: True if changed, False otherwise """ # convert other types to string before comparison if not isinstance(value, str): value = str(value) # do not change anything if a value is already configured if sysctl_read(name) == value: return True # return False if sysctl call failed if run(['sysctl', '-wq', f'{name}={value}']).returncode != 0: return False # compare old and new values # sysctl may apply value, but its actual value will be # different from requested if sysctl_read(name) == value: return True # False in other cases return False def sysctl_apply(sysctl_dict: dict[str, str], revert: bool = True) -> bool: """Apply sysctl values. Args: sysctl_dict (dict[str, str]): dictionary with sysctl keys with values revert (bool, optional): Revert to original values if new were not applied. Defaults to True. Returns: bool: True if all params configured properly, False in other cases """ # get current values sysctl_original: dict[str, str] = {} for key_name in sysctl_dict.keys(): sysctl_original[key_name] = sysctl_read(key_name) # apply new values and revert in case one of them was not applied for key_name, value in sysctl_dict.items(): if not sysctl_write(key_name, value): if revert: sysctl_apply(sysctl_original, revert=False) return False # everything applied return True def find_device_file(device): """ Recurively search /dev for the given device file and return its full path. If no device file was found 'None' is returned """ from fnmatch import fnmatch for root, dirs, files in os.walk('/dev'): for basename in files: if fnmatch(basename, device): return os.path.join(root, basename) return None def load_as_module(name: str, path: str): import importlib.util spec = importlib.util.spec_from_file_location(name, path) mod = importlib.util.module_from_spec(spec) spec.loader.exec_module(mod) return mod + +def load_as_module_source(name: str, path: str): + """ Necessary modification of load_as_module for files without *.py + extension """ + import importlib.util + from importlib.machinery import SourceFileLoader + + loader = SourceFileLoader(name, path) + spec = importlib.util.spec_from_loader(name, loader) + mod = importlib.util.module_from_spec(spec) + spec.loader.exec_module(mod) + return mod diff --git a/src/helpers/system-versions-foot.py b/src/helpers/add-system-version.py similarity index 61% rename from src/helpers/system-versions-foot.py rename to src/helpers/add-system-version.py index 9614f0d28..5270ee7d3 100755 --- a/src/helpers/system-versions-foot.py +++ b/src/helpers/add-system-version.py @@ -1,28 +1,20 @@ #!/usr/bin/python3 -# Copyright 2019, 2022 VyOS maintainers and contributors <maintainers@vyos.io> +# Copyright 2019-2024 VyOS maintainers and contributors <maintainers@vyos.io> # # 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 <http://www.gnu.org/licenses/>. -import sys -import vyos.defaults -from vyos.component_version import write_system_footer +from vyos.component_version import add_system_version -sys.stdout.write("\n\n") -if vyos.defaults.cfg_vintage == 'vyos': - write_system_footer(None, vintage='vyos') -elif vyos.defaults.cfg_vintage == 'vyatta': - write_system_footer(None, vintage='vyatta') -else: - write_system_footer(None, vintage='vyos') +add_system_version() diff --git a/src/helpers/run-config-migration.py b/src/helpers/run-config-migration.py index ce647ad0a..e6ce97363 100755 --- a/src/helpers/run-config-migration.py +++ b/src/helpers/run-config-migration.py @@ -1,86 +1,78 @@ -#!/usr/bin/python3 - -# Copyright 2019 VyOS maintainers and contributors <maintainers@vyos.io> +#!/usr/bin/env python3 +# +# Copyright (C) 2019-2024 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 program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. # -# This library is distributed in the hope that it will be useful, +# This program 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. +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU 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 <http://www.gnu.org/licenses/>. +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. import os import sys -import argparse -import datetime - -from vyos.utils.process import cmd -from vyos.migrator import Migrator, VirtualMigrator - -def main(): - argparser = argparse.ArgumentParser( - formatter_class=argparse.RawTextHelpFormatter) - argparser.add_argument('config_file', type=str, - help="configuration file to migrate") - argparser.add_argument('--force', action='store_true', - help="Force calling of all migration scripts.") - argparser.add_argument('--set-vintage', type=str, - choices=['vyatta', 'vyos'], - help="Set the format for the config version footer in config" - " file:\n" - "set to 'vyatta':\n" - "(for '/* === vyatta-config-version ... */' format)\n" - "or 'vyos':\n" - "(for '// vyos-config-version ...' format).") - argparser.add_argument('--virtual', action='store_true', - help="Update the format of the trailing comments in" - " config file,\nfrom 'vyatta' to 'vyos'; no migration" - " scripts are run.") - args = argparser.parse_args() +import time +from argparse import ArgumentParser +from shutil import copyfile - config_file_name = args.config_file - force_on = args.force - vintage = args.set_vintage - virtual = args.virtual +from vyos.migrate import ConfigMigrate +from vyos.migrate import ConfigMigrateError - if not os.access(config_file_name, os.R_OK): - print("Read error: {}.".format(config_file_name)) - sys.exit(1) +parser = ArgumentParser() +parser.add_argument('config_file', type=str, + help="configuration file to migrate") +parser.add_argument('--test-script', type=str, + help="test named script") +parser.add_argument('--output-file', type=str, + help="write to named output file instead of config file") +parser.add_argument('--force', action='store_true', + help="force run of all migration scripts") - if not os.access(config_file_name, os.W_OK): - print("Write error: {}.".format(config_file_name)) - sys.exit(1) +args = parser.parse_args() - separator = "." - backup_file_name = separator.join([config_file_name, - '{0:%Y-%m-%d-%H%M%S}'.format(datetime.datetime.now()), - 'pre-migration']) +config_file = args.config_file +out_file = args.output_file +test_script = args.test_script +force = args.force - cmd(f'cp -p {config_file_name} {backup_file_name}') +if not os.access(config_file, os.R_OK): + print(f"Config file '{config_file}' not readable") + sys.exit(1) - if not virtual: - virtual_migration = VirtualMigrator(config_file_name) - virtual_migration.run() +if out_file is None: + if not os.access(config_file, os.W_OK): + print(f"Config file '{config_file}' not writeable") + sys.exit(1) +else: + try: + open(out_file, 'w').close() + except OSError: + print(f"Output file '{out_file}' not writeable") + sys.exit(1) - migration = Migrator(config_file_name, force=force_on) - migration.run() +config_migrate = ConfigMigrate(config_file, force=force, output_file=out_file) - if not migration.config_changed(): - os.remove(backup_file_name) - else: - virtual_migration = VirtualMigrator(config_file_name, - set_vintage=vintage) +if test_script: + # run_script and exit + config_migrate.run_script(test_script) + sys.exit(0) - virtual_migration.run() +backup = None +if out_file is None: + timestr = time.strftime("%Y%m%d-%H%M%S") + backup = f'{config_file}.{timestr}.pre-migration' + copyfile(config_file, backup) - if not virtual_migration.config_changed(): - os.remove(backup_file_name) +try: + config_migrate.run() +except ConfigMigrateError as e: + print(f'Error: {e}') + sys.exit(1) -if __name__ == '__main__': - main() +if backup is not None and not config_migrate.config_modified: + os.unlink(backup) diff --git a/src/helpers/vyos-load-config.py b/src/helpers/vyos-load-config.py index 4ec865454..16083fd41 100755 --- a/src/helpers/vyos-load-config.py +++ b/src/helpers/vyos-load-config.py @@ -1,104 +1,99 @@ #!/usr/bin/env python3 # -# Copyright (C) 2019 VyOS maintainers and contributors +# Copyright (C) 2019-2024 VyOS maintainers and contributors # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License version 2 or later as # published by the Free Software Foundation. # # This program 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 General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. # # """Load config file from within config session. Config file specified by URI or path (without scheme prefix). Example: load https://somewhere.net/some.config or load /tmp/some.config """ import os import sys import gzip import tempfile import vyos.defaults import vyos.remote from vyos.configsource import ConfigSourceSession, VyOSError -from vyos.migrator import Migrator, VirtualMigrator, MigratorError +from vyos.migrate import ConfigMigrate +from vyos.migrate import ConfigMigrateError class LoadConfig(ConfigSourceSession): """A subclass for calling 'loadFile'. This does not belong in configsource.py, and only has a single caller. """ def load_config(self, path): return self._run(['/bin/cli-shell-api','loadFile',path]) file_name = sys.argv[1] if len(sys.argv) > 1 else 'config.boot' configdir = vyos.defaults.directories['config'] protocols = ['scp', 'sftp', 'http', 'https', 'ftp', 'tftp'] def get_local_config(filename): if os.path.isfile(filename): fname = filename elif os.path.isfile(os.path.join(configdir, filename)): fname = os.path.join(configdir, filename) else: sys.exit(f"No such file '{filename}'") if fname.endswith('.gz'): with gzip.open(fname, 'rb') as f: try: config_str = f.read().decode() except OSError as e: sys.exit(e) else: with open(fname, 'r') as f: try: config_str = f.read() except OSError as e: sys.exit(e) return config_str if any(file_name.startswith(f'{x}://') for x in protocols): config_string = vyos.remote.get_remote_config(file_name) if not config_string: sys.exit(f"No such config file at '{file_name}'") else: config_string = get_local_config(file_name) config = LoadConfig() print(f"Loading configuration from '{file_name}'") with tempfile.NamedTemporaryFile() as fp: with open(fp.name, 'w') as fd: fd.write(config_string) - virtual_migration = VirtualMigrator(fp.name) + config_migrate = ConfigMigrate(fp.name) try: - virtual_migration.run() - except MigratorError as err: - sys.exit('{}'.format(err)) - - migration = Migrator(fp.name) - try: - migration.run() - except MigratorError as err: - sys.exit('{}'.format(err)) + config_migrate.run() + except ConfigMigrateError as err: + sys.exit(err) try: config.load_config(fp.name) except VyOSError as err: - sys.exit('{}'.format(err)) + sys.exit(err) if config.session_changed(): print("Load complete. Use 'commit' to make changes effective.") else: print("No configuration changes to commit.") diff --git a/src/helpers/vyos-merge-config.py b/src/helpers/vyos-merge-config.py index 35424626e..5ef845ac2 100755 --- a/src/helpers/vyos-merge-config.py +++ b/src/helpers/vyos-merge-config.py @@ -1,111 +1,108 @@ #!/usr/bin/python3 # Copyright 2019-2024 VyOS maintainers and contributors <maintainers@vyos.io> # # 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 <http://www.gnu.org/licenses/>. import sys import tempfile import vyos.defaults import vyos.remote from vyos.config import Config from vyos.configtree import ConfigTree -from vyos.migrator import Migrator, VirtualMigrator +from vyos.migrate import ConfigMigrate +from vyos.migrate import ConfigMigrateError from vyos.utils.process import cmd from vyos.utils.process import DEVNULL if (len(sys.argv) < 2): print("Need config file name to merge.") print("Usage: merge <config file> [config path]") sys.exit(0) file_name = sys.argv[1] configdir = vyos.defaults.directories['config'] protocols = ['scp', 'sftp', 'http', 'https', 'ftp', 'tftp'] if any(x in file_name for x in protocols): config_file = vyos.remote.get_remote_config(file_name) if not config_file: sys.exit("No config file by that name.") else: canonical_path = "{0}/{1}".format(configdir, file_name) first_err = None try: with open(canonical_path, 'r') as f: config_file = f.read() except Exception as err: first_err = err try: with open(file_name, 'r') as f: config_file = f.read() except Exception as err: print(first_err) print(err) sys.exit(1) with tempfile.NamedTemporaryFile() as file_to_migrate: with open(file_to_migrate.name, 'w') as fd: fd.write(config_file) - virtual_migration = VirtualMigrator(file_to_migrate.name) - virtual_migration.run() - - migration = Migrator(file_to_migrate.name) - migration.run() - - if virtual_migration.config_changed() or migration.config_changed(): - with open(file_to_migrate.name, 'r') as fd: - config_file = fd.read() + config_migrate = ConfigMigrate(file_to_migrate.name) + try: + config_migrate.run() + except ConfigMigrateError as e: + sys.exit(e) merge_config_tree = ConfigTree(config_file) effective_config = Config() effective_config_tree = effective_config._running_config effective_cmds = effective_config_tree.to_commands() merge_cmds = merge_config_tree.to_commands() effective_cmd_list = effective_cmds.splitlines() merge_cmd_list = merge_cmds.splitlines() effective_cmd_set = set(effective_cmd_list) add_cmds = [ cmd for cmd in merge_cmd_list if cmd not in effective_cmd_set ] path = None if (len(sys.argv) > 2): path = sys.argv[2:] if (not effective_config_tree.exists(path) and not merge_config_tree.exists(path)): print("path {} does not exist in either effective or merge" " config; will use root.".format(path)) path = None else: path = " ".join(path) if path: add_cmds = [ cmd for cmd in add_cmds if path in cmd ] for add in add_cmds: try: cmd(f'/opt/vyatta/sbin/my_{add}', shell=True, stderr=DEVNULL) except OSError as err: print(err) if effective_config.session_changed(): print("Merge complete. Use 'commit' to make changes effective.") else: print("No configuration changes to commit.") diff --git a/src/helpers/vyos-save-config.py b/src/helpers/vyos-save-config.py index 518bd9864..fa2ea0ce4 100755 --- a/src/helpers/vyos-save-config.py +++ b/src/helpers/vyos-save-config.py @@ -1,73 +1,72 @@ #!/usr/bin/env python3 # # Copyright (C) 2023 VyOS maintainers and contributors # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License version 2 or later as # published by the Free Software Foundation. # # This program 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 General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. # # import os import re import sys from tempfile import NamedTemporaryFile from argparse import ArgumentParser from vyos.config import Config from vyos.remote import urlc -from vyos.component_version import system_footer +from vyos.component_version import add_system_version from vyos.defaults import directories DEFAULT_CONFIG_PATH = os.path.join(directories['config'], 'config.boot') remote_save = None parser = ArgumentParser(description='Save configuration') parser.add_argument('file', type=str, nargs='?', help='Save configuration to file') parser.add_argument('--write-json-file', type=str, help='Save JSON of configuration to file') args = parser.parse_args() file = args.file json_file = args.write_json_file if file is not None: save_file = file else: save_file = DEFAULT_CONFIG_PATH if re.match(r'\w+:/', save_file): try: remote_save = urlc(save_file) except ValueError as e: sys.exit(e) config = Config() ct = config.get_config_tree(effective=True) +# pylint: disable=consider-using-with write_file = save_file if remote_save is None else NamedTemporaryFile(delete=False).name -with open(write_file, 'w') as f: - # config_tree is None before boot configuration is complete; - # automated saves should check boot_configuration_complete - if ct is not None: - f.write(ct.to_string()) - f.write("\n") - f.write(system_footer()) + +# config_tree is None before boot configuration is complete; +# automated saves should check boot_configuration_complete +config_str = None if ct is None else ct.to_string() +add_system_version(config_str, write_file) if json_file is not None and ct is not None: try: with open(json_file, 'w') as f: f.write(ct.to_json()) except OSError as e: print(f'failed to write JSON file: {e}') if remote_save is not None: try: remote_save.upload(write_file) finally: os.remove(write_file) diff --git a/src/helpers/vyos_net_name b/src/helpers/vyos_net_name index 8c0992414..518e204f9 100755 --- a/src/helpers/vyos_net_name +++ b/src/helpers/vyos_net_name @@ -1,255 +1,257 @@ #!/usr/bin/env python3 # -# Copyright (C) 2021-2023 VyOS maintainers and contributors +# Copyright (C) 2021-2024 VyOS maintainers and contributors # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License version 2 or later as # published by the Free Software Foundation. # # This program 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 General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. import os import re import time import logging import tempfile import threading from sys import argv from vyos.configtree import ConfigTree from vyos.defaults import directories from vyos.utils.process import cmd from vyos.utils.boot import boot_configuration_complete -from vyos.migrator import VirtualMigrator +from vyos.migrate import ConfigMigrate vyos_udev_dir = directories['vyos_udev_dir'] vyos_log_dir = '/run/udev/log' vyos_log_file = os.path.join(vyos_log_dir, 'vyos-net-name') config_path = '/opt/vyatta/etc/config/config.boot' lock = threading.Lock() try: os.mkdir(vyos_log_dir) except FileExistsError: pass logging.basicConfig(filename=vyos_log_file, level=logging.DEBUG) def is_available(intfs: dict, intf_name: str) -> bool: """ Check if interface name is already assigned """ if intf_name in list(intfs.values()): return False return True def find_available(intfs: dict, prefix: str) -> str: """ Find lowest indexed iterface name that is not assigned """ index_list = [int(x.replace(prefix, '')) for x in list(intfs.values()) if prefix in x] index_list.sort() # find 'holes' in list, if any missing = sorted(set(range(index_list[0], index_list[-1])) - set(index_list)) if missing: return f'{prefix}{missing[0]}' return f'{prefix}{len(index_list)}' def mod_ifname(ifname: str) -> str: """ Check interface with names eX and return ifname on the next format eth{ifindex} - 2 """ if re.match("^e[0-9]+$", ifname): intf = ifname.split("e") if intf[1]: if int(intf[1]) >= 2: return "eth" + str(int(intf[1]) - 2) else: return "eth" + str(intf[1]) return ifname def get_biosdevname(ifname: str) -> str: """ Use legacy vyatta-biosdevname to query for name This is carried over for compatability only, and will likely be dropped going forward. XXX: This throws an error, and likely has for a long time, unnoticed since vyatta_net_name redirected stderr to /dev/null. """ intf = mod_ifname(ifname) if 'eth' not in intf: return intf if os.path.isdir('/proc/xen'): return intf time.sleep(1) try: biosname = cmd(f'/sbin/biosdevname --policy all_ethN -i {ifname}') except Exception as e: logging.error(f'biosdevname error: {e}') biosname = '' return intf if biosname == '' else biosname def leave_rescan_hint(intf_name: str, hwid: str): """Write interface information reported by udev This script is called while the root mount is still read-only. Leave information in /run/udev: file name, the interface; contents, the hardware id. """ try: os.mkdir(vyos_udev_dir) except FileExistsError: pass except Exception as e: logging.critical(f"Error creating rescan hint directory: {e}") exit(1) try: with open(os.path.join(vyos_udev_dir, intf_name), 'w') as f: f.write(hwid) except OSError as e: logging.critical(f"OSError {e}") def get_configfile_interfaces() -> dict: """Read existing interfaces from config file """ interfaces: dict = {} if not os.path.isfile(config_path): # If the case, then we are running off of livecd; return empty return interfaces try: with open(config_path) as f: config_file = f.read() except OSError as e: logging.critical(f"OSError {e}") exit(1) try: config = ConfigTree(config_file) except Exception: try: logging.debug(f"updating component version string syntax") # this will update the component version string syntax, # required for updates 1.2 --> 1.3/1.4 with tempfile.NamedTemporaryFile() as fp: with open(fp.name, 'w') as fd: fd.write(config_file) - virtual_migration = VirtualMigrator(fp.name) - virtual_migration.run() + config_migrate = ConfigMigrate(fp.name) + if config_migrate.syntax_update_needed(): + config_migrate.update_syntax() + config_migrate.write_config() with open(fp.name) as fd: config_file = fd.read() config = ConfigTree(config_file) except Exception as e: logging.critical(f"ConfigTree error: {e}") base = ['interfaces', 'ethernet'] if config.exists(base): eth_intfs = config.list_nodes(base) for intf in eth_intfs: path = base + [intf, 'hw-id'] if not config.exists(path): logging.warning(f"no 'hw-id' entry for {intf}") continue hwid = config.return_value(path) if hwid in list(interfaces): logging.warning(f"multiple entries for {hwid}: {interfaces[hwid]}, {intf}") continue interfaces[hwid] = intf base = ['interfaces', 'wireless'] if config.exists(base): wlan_intfs = config.list_nodes(base) for intf in wlan_intfs: path = base + [intf, 'hw-id'] if not config.exists(path): logging.warning(f"no 'hw-id' entry for {intf}") continue hwid = config.return_value(path) if hwid in list(interfaces): logging.warning(f"multiple entries for {hwid}: {interfaces[hwid]}, {intf}") continue interfaces[hwid] = intf logging.debug(f"config file entries: {interfaces}") return interfaces def add_assigned_interfaces(intfs: dict): """Add interfaces found by previous invocation of udev rule """ if not os.path.isdir(vyos_udev_dir): return for intf in os.listdir(vyos_udev_dir): path = os.path.join(vyos_udev_dir, intf) try: with open(path) as f: hwid = f.read().rstrip() except OSError as e: logging.error(f"OSError {e}") continue intfs[hwid] = intf def on_boot_event(intf_name: str, hwid: str, predefined: str = '') -> str: """Called on boot by vyos-router: 'coldplug' in vyatta_net_name """ logging.info(f"lookup {intf_name}, {hwid}") interfaces = get_configfile_interfaces() logging.debug(f"config file interfaces are {interfaces}") if hwid in list(interfaces): logging.info(f"use mapping from config file: '{hwid}' -> '{interfaces[hwid]}'") return interfaces[hwid] add_assigned_interfaces(interfaces) logging.debug(f"adding assigned interfaces: {interfaces}") if predefined: newname = predefined logging.info(f"predefined interface name for '{intf_name}' is '{newname}'") else: newname = get_biosdevname(intf_name) logging.info(f"biosdevname returned '{newname}' for '{intf_name}'") if not is_available(interfaces, newname): prefix = re.sub(r'\d+$', '', newname) newname = find_available(interfaces, prefix) logging.info(f"new name for '{intf_name}' is '{newname}'") leave_rescan_hint(newname, hwid) return newname def hotplug_event(): # Not yet implemented, since interface-rescan will only be run on boot. pass if len(argv) > 3: predef_name = argv[3] else: predef_name = '' lock.acquire() if not boot_configuration_complete(): res = on_boot_event(argv[1], argv[2], predefined=predef_name) logging.debug(f"on boot, returned name is {res}") print(res) else: logging.debug("boot configuration complete") lock.release() diff --git a/src/init/vyos-router b/src/init/vyos-router index 59004fdc1..8825cc16a 100755 --- a/src/init/vyos-router +++ b/src/init/vyos-router @@ -1,575 +1,575 @@ #!/bin/bash # Copyright (C) 2021-2024 VyOS maintainers and contributors # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License version 2 or later as # published by the Free Software Foundation. # # This program 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 General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. . /lib/lsb/init-functions : ${vyatta_env:=/etc/default/vyatta} source $vyatta_env declare progname=${0##*/} declare action=$1; shift declare -x BOOTFILE=$vyatta_sysconfdir/config/config.boot declare -x DEFAULT_BOOTFILE=$vyatta_sysconfdir/config.boot.default # If vyos-config= boot option is present, use that file instead for x in $(cat /proc/cmdline); do [[ $x = vyos-config=* ]] || continue VYOS_CONFIG="${x#vyos-config=}" done if [ ! -z "$VYOS_CONFIG" ]; then if [ -r "$VYOS_CONFIG" ]; then echo "Config selected manually: $VYOS_CONFIG" declare -x BOOTFILE="$VYOS_CONFIG" else echo "WARNING: Could not read selected config file, using default!" fi fi declare -a subinit declare -a all_subinits=( firewall ) if [ $# -gt 0 ] ; then for s in $@ ; do [ -x ${vyatta_sbindir}/${s}.init ] && subinit[${#subinit}]=$s done else for s in ${all_subinits[@]} ; do [ -x ${vyatta_sbindir}/${s}.init ] && subinit[${#subinit}]=$s done fi GROUP=vyattacfg # easy way to make empty file without any command empty() { >$1 } # check if bootup of this portion is disabled disabled () { grep -q -w no-vyos-$1 /proc/cmdline } # Load encrypted config volume mount_encrypted_config() { persist_path=$(/opt/vyatta/sbin/vyos-persistpath) if [ $? == 0 ]; then if [ -e $persist_path/boot ]; then image_name=$(cat /proc/cmdline | sed -e s+^.*vyos-union=/boot/++ | sed -e 's/ .*$//') if [ -z "$image_name" ]; then return fi if [ ! -f $persist_path/luks/$image_name ]; then return fi vyos_tpm_key=$(python3 -c 'from vyos.tpm import read_tpm_key; print(read_tpm_key().decode())' 2>/dev/null) if [ $? -ne 0 ]; then echo "ERROR: Failed to fetch encryption key from TPM. Encrypted config volume has not been mounted" echo "Use 'encryption load' to load volume with recovery key" echo "or 'encryption disable' to decrypt volume with recovery key" return fi echo $vyos_tpm_key | tr -d '\r\n' | cryptsetup open $persist_path/luks/$image_name vyos_config --key-file=- if [ $? -ne 0 ]; then echo "ERROR: Failed to decrypt config volume. Encrypted config volume has not been mounted" echo "Use 'encryption load' to load volume with recovery key" echo "or 'encryption disable' to decrypt volume with recovery key" return fi mount /dev/mapper/vyos_config /config mount /dev/mapper/vyos_config $vyatta_sysconfdir/config echo "Mounted encrypted config volume" fi fi } unmount_encrypted_config() { persist_path=$(/opt/vyatta/sbin/vyos-persistpath) if [ $? == 0 ]; then if [ -e $persist_path/boot ]; then image_name=$(cat /proc/cmdline | sed -e s+^.*vyos-union=/boot/++ | sed -e 's/ .*$//') if [ -z "$image_name" ]; then return fi if [ ! -f $persist_path/luks/$image_name ]; then return fi umount /config umount $vyatta_sysconfdir/config cryptsetup close vyos_config fi fi } # if necessary, provide initial config init_bootfile () { # define and version default boot config if not present if [ ! -r $DEFAULT_BOOTFILE ]; then if [ -f $vyos_data_dir/config.boot.default ]; then cp $vyos_data_dir/config.boot.default $DEFAULT_BOOTFILE - $vyos_libexec_dir/system-versions-foot.py >> $DEFAULT_BOOTFILE + $vyos_libexec_dir/add-system-version.py >> $DEFAULT_BOOTFILE fi fi if [ ! -r $BOOTFILE ] ; then if [ -f $DEFAULT_BOOTFILE ]; then cp $DEFAULT_BOOTFILE $BOOTFILE else - $vyos_libexec_dir/system-versions-foot.py > $BOOTFILE + $vyos_libexec_dir/add-system-version.py > $BOOTFILE fi chgrp ${GROUP} $BOOTFILE chmod 660 $BOOTFILE fi } # if necessary, migrate initial config migrate_bootfile () { if [ -x $vyos_libexec_dir/run-config-migration.py ]; then log_progress_msg migrate sg ${GROUP} -c "$vyos_libexec_dir/run-config-migration.py $BOOTFILE" fi } # configure system-specific settings system_config () { if [ -x $vyos_libexec_dir/run-config-activation.py ]; then log_progress_msg system sg ${GROUP} -c "$vyos_libexec_dir/run-config-activation.py $BOOTFILE" fi } # load the initial config load_bootfile () { log_progress_msg configure ( if [ -f /etc/default/vyatta-load-boot ]; then # build-specific environment for boot-time config loading source /etc/default/vyatta-load-boot fi if [ -x $vyos_libexec_dir/vyos-boot-config-loader.py ]; then sg ${GROUP} -c "$vyos_libexec_dir/vyos-boot-config-loader.py $BOOTFILE" fi ) } # restore if missing pre-config script restore_if_missing_preconfig_script () { if [ ! -x ${vyatta_sysconfdir}/config/scripts/vyos-preconfig-bootup.script ]; then mkdir -p ${vyatta_sysconfdir}/config/scripts chgrp ${GROUP} ${vyatta_sysconfdir}/config/scripts chmod 775 ${vyatta_sysconfdir}/config/scripts cp ${vyos_rootfs_dir}/opt/vyatta/etc/config/scripts/vyos-preconfig-bootup.script ${vyatta_sysconfdir}/config/scripts/ chgrp ${GROUP} ${vyatta_sysconfdir}/config/scripts/vyos-preconfig-bootup.script chmod 750 ${vyatta_sysconfdir}/config/scripts/vyos-preconfig-bootup.script fi } # execute the pre-config script run_preconfig_script () { if [ -x $vyatta_sysconfdir/config/scripts/vyos-preconfig-bootup.script ]; then $vyatta_sysconfdir/config/scripts/vyos-preconfig-bootup.script fi } # restore if missing post-config script restore_if_missing_postconfig_script () { if [ ! -x ${vyatta_sysconfdir}/config/scripts/vyos-postconfig-bootup.script ]; then mkdir -p ${vyatta_sysconfdir}/config/scripts chgrp ${GROUP} ${vyatta_sysconfdir}/config/scripts chmod 775 ${vyatta_sysconfdir}/config/scripts cp ${vyos_rootfs_dir}/opt/vyatta/etc/config/scripts/vyos-postconfig-bootup.script ${vyatta_sysconfdir}/config/scripts/ chgrp ${GROUP} ${vyatta_sysconfdir}/config/scripts/vyos-postconfig-bootup.script chmod 750 ${vyatta_sysconfdir}/config/scripts/vyos-postconfig-bootup.script fi } # execute the post-config scripts run_postconfig_scripts () { if [ -x $vyatta_sysconfdir/config/scripts/vyatta-postconfig-bootup.script ]; then $vyatta_sysconfdir/config/scripts/vyatta-postconfig-bootup.script fi if [ -x $vyatta_sysconfdir/config/scripts/vyos-postconfig-bootup.script ]; then $vyatta_sysconfdir/config/scripts/vyos-postconfig-bootup.script fi } run_postupgrade_script () { if [ -f $vyatta_sysconfdir/config/.upgraded ]; then # Run the system script /usr/libexec/vyos/system/post-upgrade # Run user scripts if [ -d $vyatta_sysconfdir/config/scripts/post-upgrade.d ]; then run-parts $vyatta_sysconfdir/config/scripts/post-upgrade.d fi rm -f $vyatta_sysconfdir/config/.upgraded fi } # # On image booted machines, we need to mount /boot from the image-specific # boot directory so that kernel package installation will put the # files in the right place. We also have to mount /boot/grub from the # system-wide grub directory so that tools that edit the grub.cfg # file will find it in the expected location. # bind_mount_boot () { persist_path=$(/opt/vyatta/sbin/vyos-persistpath) if [ $? == 0 ]; then if [ -e $persist_path/boot ]; then image_name=$(cat /proc/cmdline | sed -e s+^.*vyos-union=/boot/++ | sed -e 's/ .*$//') if [ -n "$image_name" ]; then mount --bind $persist_path/boot/$image_name /boot if [ $? -ne 0 ]; then echo "Couldn't bind mount /boot" fi if [ ! -d /boot/grub ]; then mkdir /boot/grub fi mount --bind $persist_path/boot/grub /boot/grub if [ $? -ne 0 ]; then echo "Couldn't bind mount /boot/grub" fi fi fi fi } clear_or_override_config_files () { for conf in snmp/snmpd.conf snmp/snmptrapd.conf snmp/snmp.conf \ keepalived/keepalived.conf cron.d/vyos-crontab \ ipvsadm.rules default/ipvsadm resolv.conf do if [ -s /etc/$conf ] ; then empty /etc/$conf chmod 0644 /etc/$conf fi done } update_interface_config () { if [ -d /run/udev/vyos ]; then $vyos_libexec_dir/vyos-interface-rescan.py $BOOTFILE fi } cleanup_post_commit_hooks () { # Remove links from the post-commit hooks directory. # note that this approach only supports hooks that are "configured", # i.e., it does not support hooks that need to always be present. cpostdir=$(cli-shell-api getPostCommitHookDir) # exclude commit hooks that need to always be present excluded="00vyos-sync 10vyatta-log-commit.pl 99vyos-user-postcommit-hooks" if [ -d "$cpostdir" ]; then for f in $cpostdir/*; do if [[ ! $excluded =~ $(basename $f) ]]; then rm -f $cpostdir/$(basename $f) fi done fi } # These are all the default security setting which are later # overridden when configuration is read. These are the values the # system defaults. security_reset () { # restore NSS cofniguration back to sane system defaults # will be overwritten later when configuration is loaded cat <<EOF >/etc/nsswitch.conf passwd: files group: files shadow: files gshadow: files # Per T2678, commenting out myhostname hosts: files dns #myhostname networks: files protocols: db files services: db files ethers: db files rpc: db files netgroup: nis EOF # restore PAM back to virgin state (no radius/tacacs services) pam-auth-update --disable radius-mandatory radius-optional rm -f /etc/pam_radius_auth.conf pam-auth-update --disable tacplus-mandatory tacplus-optional rm -f /etc/tacplus_nss.conf /etc/tacplus_servers # and no Google authenticator for 2FA/MFA pam-auth-update --disable mfa-google-authenticator # Certain configuration files are re-generated by the configuration # subsystem and must reside under /etc and can not easily be moved to /run. # So on every boot we simply delete any remaining files and let the CLI # regenearte them. # PPPoE rm -f /etc/ppp/peers/pppoe* /etc/ppp/peers/wlm* # IPSec rm -rf /etc/ipsec.conf /etc/ipsec.secrets find /etc/swanctl -type f | xargs rm -f # limit cleanup rm -f /etc/security/limits.d/10-vyos.conf # iproute2 cleanup rm -f /etc/iproute2/rt_tables.d/vyos-*.conf # Container rm -f /etc/containers/storage.conf /etc/containers/registries.conf /etc/containers/containers.conf # Clean all networks and re-create them from our CLI rm -f /etc/containers/networks/* # System Options (SSH/cURL) rm -f /etc/ssh/ssh_config.d/*vyos*.conf rm -f /etc/curlrc } # XXX: T3885 - generate persistend DHCPv6 DUID (Type4 - UUID based) gen_duid () { DUID_FILE="/var/lib/dhcpv6/dhcp6c_duid" UUID_FILE="/sys/class/dmi/id/product_uuid" UUID_FILE_ALT="/sys/class/dmi/id/product_serial" if [ ! -f ${UUID_FILE} ] && [ ! -f ${UUID_FILE_ALT} ]; then return 1 fi # DUID is based on the BIOS/EFI UUID. We omit additional - characters if [ -f ${UUID_FILE} ]; then UUID=$(cat ${UUID_FILE} | tr -d -) fi if [ -z ${UUID} ]; then UUID=$(uuidgen --sha1 --namespace @dns --name $(cat ${UUID_FILE_ALT}) | tr -d -) fi # Add DUID type4 (UUID) information DUID_TYPE="0004" # The length-information (as per RFC6355 UUID is 128 bits long) is in big-endian # format - beware when porting to ARM64. The length field consists out of the # UUID (128 bit + 16 bits DUID type) resulting in hex 12. DUID_LEN="0012" if [ "$(echo -n I | od -to2 | head -n1 | cut -f2 -d" " | cut -c6 )" -eq 1 ]; then # true on little-endian (x86) systems DUID_LEN="1200" fi for i in $(echo -n ${DUID_LEN}${DUID_TYPE}${UUID} | sed 's/../& /g'); do echo -ne "\x$i" done > ${DUID_FILE} } start () { # reset and clean config files security_reset || log_failure_msg "security reset failed" # some legacy directories migrated over from old rl-system.init mkdir -p /var/run/vyatta /var/log/vyatta chgrp vyattacfg /var/run/vyatta /var/log/vyatta chmod 775 /var/run/vyatta /var/log/vyatta log_daemon_msg "Waiting for NICs to settle down" # On boot time udev migth take a long time to reorder nic's, this will ensure that # all udev activity is completed and all nics presented at boot-time will have their # final name before continuing with vyos-router initialization. SECONDS=0 udevadm settle STATUS=$? log_progress_msg "settled in ${SECONDS}sec." log_end_msg ${STATUS} # mountpoint for bpf maps required by xdp mount -t bpf none /sys/fs/bpf # Clear out Debian APT source config file empty /etc/apt/sources.list # Generate DHCPv6 DUID gen_duid || log_failure_msg "could not generate DUID" # Mount a temporary filesystem for container networks. # Configuration should be loaded from VyOS cli. cni_dir="/etc/cni/net.d" [ ! -d ${cni_dir} ] && mkdir -p ${cni_dir} mount -t tmpfs none ${cni_dir} # Init firewall nfct helper add rpc inet tcp nfct helper add rpc inet udp nfct helper add tns inet tcp nfct helper add rpc inet6 tcp nfct helper add rpc inet6 udp nfct helper add tns inet6 tcp nft --file /usr/share/vyos/vyos-firewall-init.conf || log_failure_msg "could not initiate firewall rules" # As VyOS does not execute commands that are not present in the CLI we call # the script by hand to have a single source for the login banner and MOTD ${vyos_conf_scripts_dir}/system_console.py || log_failure_msg "could not reset serial console" ${vyos_conf_scripts_dir}/system_login_banner.py || log_failure_msg "could not reset motd and issue files" ${vyos_conf_scripts_dir}/system_option.py || log_failure_msg "could not reset system option files" ${vyos_conf_scripts_dir}/system_ip.py || log_failure_msg "could not reset system IPv4 options" ${vyos_conf_scripts_dir}/system_ipv6.py || log_failure_msg "could not reset system IPv6 options" ${vyos_conf_scripts_dir}/system_conntrack.py || log_failure_msg "could not reset conntrack subsystem" ${vyos_conf_scripts_dir}/container.py || log_failure_msg "could not reset container subsystem" clear_or_override_config_files || log_failure_msg "could not reset config files" # enable some debugging before loading the configuration if grep -q vyos-debug /proc/cmdline; then log_action_begin_msg "Enable runtime debugging options" touch /tmp/vyos.container.debug touch /tmp/vyos.ifconfig.debug touch /tmp/vyos.frr.debug touch /tmp/vyos.container.debug touch /tmp/vyos.smoketest.debug fi log_action_begin_msg "Mounting VyOS Config" # ensure the vyatta_configdir supports a large number of inodes since # the config hierarchy is often inode-bound (instead of size). # impose a minimum and then scale up dynamically with the actual size # of the system memory. local tmem=$(sed -n 's/^MemTotal: \+\([0-9]\+\) kB$/\1/p' /proc/meminfo) local tpages local tmpfs_opts="nosuid,nodev,mode=775,nr_inodes=0" #automatically allocate inodes mount -o $tmpfs_opts -t tmpfs none ${vyatta_configdir} \ && chgrp ${GROUP} ${vyatta_configdir} log_action_end_msg $? mount_encrypted_config # T5239: early read of system hostname as this value is read-only once during # FRR initialisation tmp=$(${vyos_libexec_dir}/read-saved-value.py --path "system host-name") hostnamectl set-hostname --static "$tmp" ${vyos_conf_scripts_dir}/system_frr.py || log_failure_msg "could not reset FRR config" # If for any reason FRR was not started by system_frr.py - start it anyways. # This is a safety net! systemctl start frr.service disabled bootfile || init_bootfile cleanup_post_commit_hooks log_daemon_msg "Starting VyOS router" disabled migrate || migrate_bootfile restore_if_missing_preconfig_script run_preconfig_script run_postupgrade_script update_interface_config disabled system_config || system_config for s in ${subinit[@]} ; do if ! disabled $s; then log_progress_msg $s if ! ${vyatta_sbindir}/${s}.init start then log_failure_msg exit 1 fi fi done bind_mount_boot disabled configure || load_bootfile log_end_msg $? telinit q chmod g-w,o-w / restore_if_missing_postconfig_script run_postconfig_scripts tmp=$(${vyos_libexec_dir}/read-saved-value.py --path "protocols rpki cache") if [[ ! -z "$tmp" ]]; then vtysh -c "rpki start" fi } stop() { local -i status=0 log_daemon_msg "Stopping VyOS router" for ((i=${#sub_inits[@]} - 1; i >= 0; i--)) ; do s=${subinit[$i]} log_progress_msg $s ${vyatta_sbindir}/${s}.init stop let status\|=$? done log_end_msg $status log_action_begin_msg "Un-mounting VyOS Config" umount ${vyatta_configdir} log_action_end_msg $? systemctl stop frr.service unmount_encrypted_config } case "$action" in start) start ;; stop) stop ;; restart|force-reload) stop && start ;; *) log_failure_msg "usage: $progname [ start|stop|restart ] [ subinit ... ]" ; false ;; esac exit $? # Local Variables: # mode: shell-script # sh-indentation: 4 # End: diff --git a/src/migration-scripts/bgp/0-to-1 b/src/migration-scripts/bgp/0-to-1 old mode 100755 new mode 100644 index 5b8e8a163..a2f3343d8 --- a/src/migration-scripts/bgp/0-to-1 +++ b/src/migration-scripts/bgp/0-to-1 @@ -1,60 +1,40 @@ -#!/usr/bin/env python3 +# Copyright 2021-2024 VyOS maintainers and contributors <maintainers@vyos.io> # -# Copyright (C) 2021 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 program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, +# 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 General Public License for more details. +# 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 General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. # T3417: migrate BGP tagNode to node as we can only have one BGP process -from sys import argv -from sys import exit - from vyos.configtree import ConfigTree -if len(argv) < 2: - print("Must specify file name!") - exit(1) - -file_name = argv[1] - -with open(file_name, 'r') as f: - config_file = f.read() - -base = ['protocols', 'bgp'] -config = ConfigTree(config_file) - -if not config.exists(base) or not config.is_tag(base): - # Nothing to do - exit(0) +def migrate(config: ConfigTree) -> None: + base = ['protocols', 'bgp'] -# Only one BGP process is supported, thus this operation is savea -asn = config.list_nodes(base) -bgp_base = base + asn + if not config.exists(base) or not config.is_tag(base): + # Nothing to do + return -# We need a temporary copy of the config -tmp_base = ['protocols', 'bgp2'] -config.copy(bgp_base, tmp_base) + # Only one BGP process is supported, thus this operation is savea + asn = config.list_nodes(base) + bgp_base = base + asn -# Now it's save to delete the old configuration -config.delete(base) + # We need a temporary copy of the config + tmp_base = ['protocols', 'bgp2'] + config.copy(bgp_base, tmp_base) -# Rename temporary copy to new final config and set new "local-as" option -config.rename(tmp_base, 'bgp') -config.set(base + ['local-as'], value=asn[0]) + # Now it's save to delete the old configuration + config.delete(base) -try: - with open(file_name, 'w') as f: - f.write(config.to_string()) -except OSError as e: - print(f'Failed to save the modified config: {e}') - exit(1) + # Rename temporary copy to new final config and set new "local-as" option + config.rename(tmp_base, 'bgp') + config.set(base + ['local-as'], value=asn[0]) diff --git a/src/migration-scripts/bgp/1-to-2 b/src/migration-scripts/bgp/1-to-2 old mode 100755 new mode 100644 index a40d86e67..c0fc3b05a --- a/src/migration-scripts/bgp/1-to-2 +++ b/src/migration-scripts/bgp/1-to-2 @@ -1,84 +1,64 @@ -#!/usr/bin/env python3 +# Copyright 2021-2024 VyOS maintainers and contributors <maintainers@vyos.io> # -# Copyright (C) 2021-2024 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 program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, +# 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 General Public License for more details. +# 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 General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. # T3741: no-ipv4-unicast is now enabled by default # T5937: Migrate IPv6 BGP Neighbor Peer Groups -from sys import argv -from sys import exit - from vyos.configtree import ConfigTree -if len(argv) < 2: - print("Must specify file name!") - exit(1) - -file_name = argv[1] +base = ['protocols', 'bgp'] -with open(file_name, 'r') as f: - config_file = f.read() +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + # Nothing to do + return -base = ['protocols', 'bgp'] -config = ConfigTree(config_file) + # This is now a default option - simply delete it. + # As it was configured explicitly - we can also bail out early as we need to + # do nothing! + if config.exists(base + ['parameters', 'default', 'no-ipv4-unicast']): + config.delete(base + ['parameters', 'default', 'no-ipv4-unicast']) -if not config.exists(base): - # Nothing to do - exit(0) + # Check if the "default" node is now empty, if so - remove it + if len(config.list_nodes(base + ['parameters', 'default'])) == 0: + config.delete(base + ['parameters', 'default']) -# This is now a default option - simply delete it. -# As it was configured explicitly - we can also bail out early as we need to -# do nothing! -if config.exists(base + ['parameters', 'default', 'no-ipv4-unicast']): - config.delete(base + ['parameters', 'default', 'no-ipv4-unicast']) + # Check if the "default" node is now empty, if so - remove it + if len(config.list_nodes(base + ['parameters'])) == 0: + config.delete(base + ['parameters']) + else: + # As we now install a new default option into BGP we need to migrate all + # existing BGP neighbors and restore the old behavior + if config.exists(base + ['neighbor']): + for neighbor in config.list_nodes(base + ['neighbor']): + peer_group = base + ['neighbor', neighbor, 'peer-group'] + if config.exists(peer_group): + peer_group_name = config.return_value(peer_group) + # peer group enables old behavior for neighbor - bail out + if config.exists(base + ['peer-group', peer_group_name, 'address-family', 'ipv4-unicast']): + continue - # Check if the "default" node is now empty, if so - remove it - if len(config.list_nodes(base + ['parameters', 'default'])) == 0: - config.delete(base + ['parameters', 'default']) + afi_ipv4 = base + ['neighbor', neighbor, 'address-family', 'ipv4-unicast'] + if not config.exists(afi_ipv4): + config.set(afi_ipv4) - # Check if the "default" node is now empty, if so - remove it - if len(config.list_nodes(base + ['parameters'])) == 0: - config.delete(base + ['parameters']) -else: - # As we now install a new default option into BGP we need to migrate all - # existing BGP neighbors and restore the old behavior + # Migrate IPv6 AFI peer-group if config.exists(base + ['neighbor']): for neighbor in config.list_nodes(base + ['neighbor']): - peer_group = base + ['neighbor', neighbor, 'peer-group'] - if config.exists(peer_group): - peer_group_name = config.return_value(peer_group) - # peer group enables old behavior for neighbor - bail out - if config.exists(base + ['peer-group', peer_group_name, 'address-family', 'ipv4-unicast']): - continue - - afi_ipv4 = base + ['neighbor', neighbor, 'address-family', 'ipv4-unicast'] - if not config.exists(afi_ipv4): - config.set(afi_ipv4) - -# Migrate IPv6 AFI peer-group -if config.exists(base + ['neighbor']): - for neighbor in config.list_nodes(base + ['neighbor']): - tmp_path = base + ['neighbor', neighbor, 'address-family', 'ipv6-unicast', 'peer-group'] - if config.exists(tmp_path): - peer_group = config.return_value(tmp_path) - config.set(base + ['neighbor', neighbor, 'peer-group'], value=peer_group) - config.delete(tmp_path) - -try: - with open(file_name, 'w') as f: - f.write(config.to_string()) -except OSError as e: - print(f'Failed to save the modified config: {e}') - exit(1) + tmp_path = base + ['neighbor', neighbor, 'address-family', 'ipv6-unicast', 'peer-group'] + if config.exists(tmp_path): + peer_group = config.return_value(tmp_path) + config.set(base + ['neighbor', neighbor, 'peer-group'], value=peer_group) + config.delete(tmp_path) diff --git a/src/migration-scripts/bgp/2-to-3 b/src/migration-scripts/bgp/2-to-3 old mode 100755 new mode 100644 index 34d321a96..d8bc34db6 --- a/src/migration-scripts/bgp/2-to-3 +++ b/src/migration-scripts/bgp/2-to-3 @@ -1,51 +1,30 @@ -#!/usr/bin/env python3 +# Copyright 2022-2024 VyOS maintainers and contributors <maintainers@vyos.io> # -# Copyright (C) 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 program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, +# 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 General Public License for more details. +# 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 General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. # T4257: Discussion on changing BGP autonomous system number syntax -from sys import argv -from sys import exit - from vyos.configtree import ConfigTree -if len(argv) < 2: - print("Must specify file name!") - exit(1) - -file_name = argv[1] - -with open(file_name, 'r') as f: - config_file = f.read() - -config = ConfigTree(config_file) - -# Check if BGP is even configured. Then check if local-as exists, then add the system-as, then remove the local-as. This is for global configuration. -if config.exists(['protocols', 'bgp']): - if config.exists(['protocols', 'bgp', 'local-as']): - config.rename(['protocols', 'bgp', 'local-as'], 'system-as') - -# Check if vrf names are configured. Then check if local-as exists inside of a name, then add the system-as, then remove the local-as. This is for vrf configuration. -if config.exists(['vrf', 'name']): - for vrf in config.list_nodes(['vrf', 'name']): - if config.exists(['vrf', f'name {vrf}', 'protocols', 'bgp', 'local-as']): - config.rename(['vrf', f'name {vrf}', 'protocols', 'bgp', 'local-as'], 'system-as') - -try: - with open(file_name, 'w') as f: - f.write(config.to_string()) -except OSError as e: - print(f'Failed to save the modified config: {e}') - exit(1) +def migrate(config: ConfigTree) -> None: + # Check if BGP is even configured. Then check if local-as exists, then add the system-as, then remove the local-as. This is for global configuration. + if config.exists(['protocols', 'bgp']): + if config.exists(['protocols', 'bgp', 'local-as']): + config.rename(['protocols', 'bgp', 'local-as'], 'system-as') + + # Check if vrf names are configured. Then check if local-as exists inside of a name, then add the system-as, then remove the local-as. This is for vrf configuration. + if config.exists(['vrf', 'name']): + for vrf in config.list_nodes(['vrf', 'name']): + if config.exists(['vrf', f'name {vrf}', 'protocols', 'bgp', 'local-as']): + config.rename(['vrf', f'name {vrf}', 'protocols', 'bgp', 'local-as'], 'system-as') diff --git a/src/migration-scripts/bgp/3-to-4 b/src/migration-scripts/bgp/3-to-4 old mode 100755 new mode 100644 index 894cdda2b..842aef0ce --- a/src/migration-scripts/bgp/3-to-4 +++ b/src/migration-scripts/bgp/3-to-4 @@ -1,64 +1,43 @@ -#!/usr/bin/env python3 +# Copyright 2023-2024 VyOS maintainers and contributors <maintainers@vyos.io> # -# Copyright (C) 2023 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 program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, +# 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 General Public License for more details. +# 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 General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. # T5150: Rework CLI definitions to apply route-maps between routing daemons # and zebra/kernel -from sys import argv -from sys import exit - from vyos.configtree import ConfigTree -if len(argv) < 2: - print("Must specify file name!") - exit(1) - -file_name = argv[1] - -with open(file_name, 'r') as f: - config_file = f.read() - -config = ConfigTree(config_file) - -bgp_base = ['protocols', 'bgp'] -# Check if BGP is configured - if so, migrate the CLI node -if config.exists(bgp_base): - if config.exists(bgp_base + ['route-map']): - tmp = config.return_value(bgp_base + ['route-map']) - - config.set(['system', 'ip', 'protocol', 'bgp', 'route-map'], value=tmp) - config.set_tag(['system', 'ip', 'protocol']) - config.delete(bgp_base + ['route-map']) - - -# Check if vrf names are configured. Check if BGP is configured - if so, migrate -# the CLI node(s) -if config.exists(['vrf', 'name']): - for vrf in config.list_nodes(['vrf', 'name']): - vrf_base = ['vrf', 'name', vrf] - if config.exists(vrf_base + ['protocols', 'bgp', 'route-map']): - tmp = config.return_value(vrf_base + ['protocols', 'bgp', 'route-map']) - - config.set(vrf_base + ['ip', 'protocol', 'bgp', 'route-map'], value=tmp) - config.set_tag(vrf_base + ['ip', 'protocol', 'bgp']) - config.delete(vrf_base + ['protocols', 'bgp', 'route-map']) - -try: - with open(file_name, 'w') as f: - f.write(config.to_string()) -except OSError as e: - print(f'Failed to save the modified config: {e}') - exit(1) +def migrate(config: ConfigTree) -> None: + bgp_base = ['protocols', 'bgp'] + # Check if BGP is configured - if so, migrate the CLI node + if config.exists(bgp_base): + if config.exists(bgp_base + ['route-map']): + tmp = config.return_value(bgp_base + ['route-map']) + + config.set(['system', 'ip', 'protocol', 'bgp', 'route-map'], value=tmp) + config.set_tag(['system', 'ip', 'protocol']) + config.delete(bgp_base + ['route-map']) + + + # Check if vrf names are configured. Check if BGP is configured - if so, migrate + # the CLI node(s) + if config.exists(['vrf', 'name']): + for vrf in config.list_nodes(['vrf', 'name']): + vrf_base = ['vrf', 'name', vrf] + if config.exists(vrf_base + ['protocols', 'bgp', 'route-map']): + tmp = config.return_value(vrf_base + ['protocols', 'bgp', 'route-map']) + + config.set(vrf_base + ['ip', 'protocol', 'bgp', 'route-map'], value=tmp) + config.set_tag(vrf_base + ['ip', 'protocol', 'bgp']) + config.delete(vrf_base + ['protocols', 'bgp', 'route-map']) diff --git a/src/migration-scripts/bgp/4-to-5 b/src/migration-scripts/bgp/4-to-5 old mode 100755 new mode 100644 index c4eb9ec72..d779eb11e --- a/src/migration-scripts/bgp/4-to-5 +++ b/src/migration-scripts/bgp/4-to-5 @@ -1,67 +1,46 @@ -#!/usr/bin/env python3 +# Copyright 2024 VyOS maintainers and contributors <maintainers@vyos.io> # -# Copyright (C) 2024 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 program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, +# 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 General Public License for more details. +# 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 General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. # Delete 'protocols bgp address-family ipv6-unicast route-target vpn # import/export', if 'protocols bgp address-family ipv6-unicast # route-target vpn both' exists -from sys import argv -from sys import exit - from vyos.configtree import ConfigTree -if len(argv) < 2: - print("Must specify file name!") - exit(1) - -file_name = argv[1] - -with open(file_name, 'r') as f: - config_file = f.read() - -config = ConfigTree(config_file) - -bgp_base = ['protocols', 'bgp'] -# Delete 'import/export' in default vrf if 'both' exists -if config.exists(bgp_base): - for address_family in ['ipv4-unicast', 'ipv6-unicast']: - rt_path = bgp_base + ['address-family', address_family, 'route-target', - 'vpn'] - if config.exists(rt_path + ['both']): - if config.exists(rt_path + ['import']): - config.delete(rt_path + ['import']) - if config.exists(rt_path + ['export']): - config.delete(rt_path + ['export']) - -# Delete import/export in vrfs if both exists -if config.exists(['vrf', 'name']): - for vrf in config.list_nodes(['vrf', 'name']): - vrf_base = ['vrf', 'name', vrf] +def migrate(config: ConfigTree) -> None: + bgp_base = ['protocols', 'bgp'] + # Delete 'import/export' in default vrf if 'both' exists + if config.exists(bgp_base): for address_family in ['ipv4-unicast', 'ipv6-unicast']: - rt_path = vrf_base + bgp_base + ['address-family', address_family, - 'route-target', 'vpn'] + rt_path = bgp_base + ['address-family', address_family, 'route-target', + 'vpn'] if config.exists(rt_path + ['both']): if config.exists(rt_path + ['import']): config.delete(rt_path + ['import']) if config.exists(rt_path + ['export']): config.delete(rt_path + ['export']) -try: - with open(file_name, 'w') as f: - f.write(config.to_string()) -except OSError as e: - print(f'Failed to save the modified config: {e}') - exit(1) + # Delete import/export in vrfs if both exists + if config.exists(['vrf', 'name']): + for vrf in config.list_nodes(['vrf', 'name']): + vrf_base = ['vrf', 'name', vrf] + for address_family in ['ipv4-unicast', 'ipv6-unicast']: + rt_path = vrf_base + bgp_base + ['address-family', address_family, + 'route-target', 'vpn'] + if config.exists(rt_path + ['both']): + if config.exists(rt_path + ['import']): + config.delete(rt_path + ['import']) + if config.exists(rt_path + ['export']): + config.delete(rt_path + ['export']) diff --git a/src/migration-scripts/cluster/1-to-2 b/src/migration-scripts/cluster/1-to-2 old mode 100755 new mode 100644 index a2e589155..5ca4531ea --- a/src/migration-scripts/cluster/1-to-2 +++ b/src/migration-scripts/cluster/1-to-2 @@ -1,193 +1,178 @@ -#!/usr/bin/env python3 +# Copyright 2023-2024 VyOS maintainers and contributors <maintainers@vyos.io> # -# Copyright (C) 2023 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 program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, +# 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 General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. +# 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 <http://www.gnu.org/licenses/>. import re import sys from vyos.configtree import ConfigTree +from vyos.base import MigrationError -if __name__ == '__main__': - if len(sys.argv) < 2: - print("Must specify file name!") - sys.exit(1) - - file_name = sys.argv[1] - - with open(file_name, 'r') as f: - config_file = f.read() - - config = ConfigTree(config_file) - +def migrate(config: ConfigTree) -> None: if not config.exists(['cluster']): # Cluster is not set -- nothing to do at all - sys.exit(0) + return # If at least one cluster group is defined, we have real work to do. # If there are no groups, we remove the top-level cluster node at the end of this script anyway. if config.exists(['cluster', 'group']): # First, gather timer and interface settings to duplicate them in all groups, # since in the old cluster they are global, but in VRRP they are always per-group global_interface = None if config.exists(['cluster', 'interface']): global_interface = config.return_value(['cluster', 'interface']) else: # Such configs shouldn't exist in practice because interface is a required option. # But since it's possible to specify interface inside 'service' options, # we may be able to convert such configs nonetheless. print("Warning: incorrect cluster config: interface is not defined.", file=sys.stderr) # There are three timers: advertise-interval, dead-interval, and monitor-dead-interval # Only the first one makes sense for the VRRP, we translate it to advertise-interval advertise_interval = None if config.exists(['cluster', 'keepalive-interval']): advertise_interval = config.return_value(['cluster', 'keepalive-interval']) if advertise_interval is not None: # Cluster had all timers in milliseconds, so we need to convert them to seconds # And ensure they are not shorter than one second advertise_interval = int(advertise_interval) // 1000 if advertise_interval < 1: advertise_interval = 1 # Cluster had password as a global option, in VRRP it's per-group password = None if config.exists(['cluster', 'pre-shared-secret']): password = config.return_value(['cluster', 'pre-shared-secret']) # Set up the stage for converting cluster groups to VRRP groups free_vrids = set(range(1,255)) vrrp_base_path = ['high-availability', 'vrrp', 'group'] if not config.exists(vrrp_base_path): # If VRRP is not set up, create a node and set it to 'tag node' # Setting it to 'tag' is not mandatory but it's better to be consistent # with configs produced by 'save' config.set(vrrp_base_path) config.set_tag(vrrp_base_path) else: # If there are VRRP groups already, we need to find the set of unused VRID numbers to avoid conflicts existing_vrids = set() for vg in config.list_nodes(vrrp_base_path): existing_vrids.add(int(config.return_value(vrrp_base_path + [vg, 'vrid']))) free_vrids = free_vrids.difference(existing_vrids) # Now handle cluster groups groups = config.list_nodes(['cluster', 'group']) for g in groups: base_path = ['cluster', 'group', g] service_names = config.return_values(base_path + ['service']) # Cluster used to allow services other than IP addresses, at least nominally # Whether that ever worked is a big question, but we need to consider that, # since configs with custom services are definitely impossible to meaningfully migrate now services = {"ip": [], "other": []} for s in service_names: if re.match(r'^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/\d{1,2})(/[a-z]+\d+)?$', s): services["ip"].append(s) else: services["other"].append(s) if services["other"]: - print("Cluster config includes non-IP address services and cannot be migrated", file=sys.stderr) - sys.exit(1) + err_str = "Cluster config includes non-IP address services and cannot be migrated" + print(err_str, file=sys.stderr) + raise MigrationError(err_str) # Cluster allowed virtual IPs for different interfaces within a single group. # VRRP groups are by definition bound to interfaces, so we cannot migrate such configurations. # Thus we need to find out if all addresses either leave the interface unspecified # (in that case the global 'cluster interface' option is used), # or have the same interface, or have the same interface as the global 'cluster interface'. # First, we collect all addresses and check if they have interface specified # If not, we substitute the global interface option # or throw an error if it's not in the config. ips = [] for ip in services["ip"]: ip_with_intf = re.match(r'^(?P<ip>\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\/\d{1,2})/(?P<intf>[a-z]+\d+)$', ip) if ip_with_intf: ips.append({"ip": ip_with_intf.group("ip"), "interface": ip_with_intf.group("intf")}) else: if global_interface is not None: ips.append({"ip": ip, "interface": global_interface}) else: - print("Error: cluster has groups with IPs without interfaces and 'cluster interface' is not specified.", file=sys.stderr) - sys.exit(1) + err_str = "Cluster group has addresses without interfaces and 'cluster interface' is not specified." + print(f'Error: {err_str}', file=sys.stderr) + raise MigrationError(err_str) # Then we check if all addresses are for the same interface. intfs_set = set(map(lambda i: i["interface"], ips)) if len(intfs_set) > 1: - print("Error: cluster group has addresses for different interfaces", file=sys.stderr) - sys.exit(1) + err_str = "Cluster group has addresses for different interfaces" + print(f'Error: {err_str}', file=sys.stderr) + raise MigrationError(err_str) # If we got this far, the group is migratable. # Extract the interface from the set -- we know there's only a single member. interface = intfs_set.pop() addresses = list(map(lambda i: i["ip"], ips)) vrrp_path = ['high-availability', 'vrrp', 'group', g] # If there's already a VRRP group with exactly the same name, # we probably shouldn't try to make up a unique name, just leave migration to the user... if config.exists(vrrp_path): - print("Error: VRRP group with the same name as a cluster group already exists", file=sys.stderr) - sys.exit(1) + err_str = "VRRP group with the same name already exists" + print(f'Error: {err_str}', file=sys.stderr) + raise MigrationError(err_str) config.set(vrrp_path + ['interface'], value=interface) for a in addresses: config.set(vrrp_path + ['virtual-address'], value=a, replace=False) # Take the next free VRID and assign it to the group vrid = free_vrids.pop() config.set(vrrp_path + ['vrid'], value=vrid) # Convert the monitor option to VRRP ping health check if config.exists(base_path + ['monitor']): monitor_ip = config.return_value(base_path + ['monitor']) config.set(vrrp_path + ['health-check', 'ping'], value=monitor_ip) # Convert "auto-failback" to "no-preempt", if necessary if config.exists(base_path + ['auto-failback']): # It's a boolean node that requires "true" or "false" # so if it exists we still need to check its value auto_failback = config.return_value(base_path + ['auto-failback']) if auto_failback == "false": config.set(vrrp_path + ['no-preempt']) else: # It's "true" or we assume it is, which means preemption is desired, # and in VRRP config it's the default pass else: # The old default for that option is false config.set(vrrp_path + ['no-preempt']) # Inject settings from the global cluster config that have to be per-group in VRRP if advertise_interval is not None: config.set(vrrp_path + ['advertise-interval'], value=advertise_interval) if password is not None: config.set(vrrp_path + ['authentication', 'password'], value=password) config.set(vrrp_path + ['authentication', 'type'], value='plaintext-password') # Finally, clean up the old cluster node config.delete(['cluster']) - - try: - with open(file_name, 'w') as f: - f.write(config.to_string()) - except OSError as e: - print("Failed to save the modified config: {}".format(e)) - sys.exit(1) diff --git a/src/migration-scripts/config-management/0-to-1 b/src/migration-scripts/config-management/0-to-1 old mode 100755 new mode 100644 index 6528fd136..44c685630 --- a/src/migration-scripts/config-management/0-to-1 +++ b/src/migration-scripts/config-management/0-to-1 @@ -1,31 +1,24 @@ -#!/usr/bin/env python3 +# Copyright 2018-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# 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 <http://www.gnu.org/licenses/>. # Add commit-revisions option if it doesn't exist -import sys - from vyos.configtree import ConfigTree -if len(sys.argv) < 2: - print("Must specify file name!") - sys.exit(1) - -file_name = sys.argv[1] - -with open(file_name, 'r') as f: - config_file = f.read() - -config = ConfigTree(config_file) - -if config.exists(['system', 'config-management', 'commit-revisions']): - # Nothing to do - sys.exit(0) -else: +def migrate(config: ConfigTree) -> None: + if config.exists(['system', 'config-management', 'commit-revisions']): + # Nothing to do + return config.set(['system', 'config-management', 'commit-revisions'], value='200') - - try: - with open(file_name, 'w') as f: - f.write(config.to_string()) - except OSError as e: - print("Failed to save the modified config: {}".format(e)) - sys.exit(1) diff --git a/src/migration-scripts/conntrack-sync/1-to-2 b/src/migration-scripts/conntrack-sync/1-to-2 old mode 100755 new mode 100644 index a8e1007f3..3e10e98c3 --- a/src/migration-scripts/conntrack-sync/1-to-2 +++ b/src/migration-scripts/conntrack-sync/1-to-2 @@ -1,66 +1,46 @@ -#!/usr/bin/env python3 +# Copyright 2021-2024 VyOS maintainers and contributors <maintainers@vyos.io> # -# Copyright (C) 2021 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 program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, +# 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 General Public License for more details. +# 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 General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. # VyOS 1.2 crux allowed configuring a lower or upper case loglevel. This # is no longer supported as the input data is validated and will lead to # an error. If user specifies an upper case logleve, make it lowercase -from sys import argv -from sys import exit - from vyos.configtree import ConfigTree -if len(argv) < 2: - print("Must specify file name!") - exit(1) - -file_name = argv[1] - -with open(file_name, 'r') as f: - config_file = f.read() - base = ['service', 'conntrack-sync'] -config = ConfigTree(config_file) -if not config.exists(base): - # Nothing to do - exit(0) -else: +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + # Nothing to do + return + base_accept_proto = base + ['accept-protocol'] if config.exists(base_accept_proto): tmp = config.return_value(base_accept_proto) config.delete(base_accept_proto) for protocol in tmp.split(','): config.set(base_accept_proto, value=protocol, replace=False) base_ignore_addr = base + ['ignore-address', 'ipv4'] if config.exists(base_ignore_addr): tmp = config.return_values(base_ignore_addr) config.delete(base_ignore_addr) for address in tmp: config.set(base + ['ignore-address'], value=address, replace=False) # we no longer support cluster mode base_cluster = base + ['failover-mechanism', 'cluster'] if config.exists(base_cluster): config.delete(base_cluster) - - try: - with open(file_name, 'w') as f: - f.write(config.to_string()) - except OSError as e: - print("Failed to save the modified config: {}".format(e)) - exit(1) diff --git a/src/migration-scripts/conntrack/1-to-2 b/src/migration-scripts/conntrack/1-to-2 old mode 100755 new mode 100644 index c4fe667fc..0a4fb3de9 --- a/src/migration-scripts/conntrack/1-to-2 +++ b/src/migration-scripts/conntrack/1-to-2 @@ -1,32 +1,26 @@ -#!/usr/bin/env python3 +# Copyright 2021-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# 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 <http://www.gnu.org/licenses/>. # Delete "set system conntrack modules gre" option -import sys - from vyos.configtree import ConfigTree -if len(sys.argv) < 2: - print("Must specify file name!") - sys.exit(1) - -file_name = sys.argv[1] -with open(file_name, 'r') as f: - config_file = f.read() +def migrate(config: ConfigTree) -> None: + if not config.exists(['system', 'conntrack', 'modules', 'gre']): + return -config = ConfigTree(config_file) - -if not config.exists(['system', 'conntrack', 'modules', 'gre']): - # Nothing to do - sys.exit(0) -else: # Delete abandoned node config.delete(['system', 'conntrack', 'modules', 'gre']) - - try: - with open(file_name, 'w') as f: - f.write(config.to_string()) - except OSError as e: - print("Failed to save the modified config: {}".format(e)) - sys.exit(1) diff --git a/src/migration-scripts/conntrack/2-to-3 b/src/migration-scripts/conntrack/2-to-3 old mode 100755 new mode 100644 index 6bb42be1e..5ad4e6350 --- a/src/migration-scripts/conntrack/2-to-3 +++ b/src/migration-scripts/conntrack/2-to-3 @@ -1,36 +1,31 @@ -#!/usr/bin/env python3 +# Copyright 2021-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# 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 <http://www.gnu.org/licenses/>. # Conntrack syntax version 3 # Enables all conntrack modules (previous default behaviour) and omits manually disabled modules. -import sys - from vyos.configtree import ConfigTree -if len(sys.argv) < 2: - print('Must specify file name!') - sys.exit(1) - -filename = sys.argv[1] - -with open(filename, 'r') as f: - config = ConfigTree(f.read()) - module_path = ['system', 'conntrack', 'modules'] -# Go over all conntrack modules available as of v1.3.0. -for module in ['ftp', 'h323', 'nfs', 'pptp', 'sip', 'sqlnet', 'tftp']: - # 'disable' is being phased out. - if config.exists(module_path + [module, 'disable']): - config.delete(module_path + [module]) - # If it wasn't manually 'disable'd, it was enabled by default. - else: - config.set(module_path + [module]) - -try: - if config.exists(module_path): - with open(filename, 'w') as f: - f.write(config.to_string()) -except OSError as e: - print(f'Failed to save the modified config: {e}') - sys.exit(1) +def migrate(config: ConfigTree) -> None: + # Go over all conntrack modules available as of v1.3.0. + for module in ['ftp', 'h323', 'nfs', 'pptp', 'sip', 'sqlnet', 'tftp']: + # 'disable' is being phased out. + if config.exists(module_path + [module, 'disable']): + config.delete(module_path + [module]) + # If it wasn't manually 'disable'd, it was enabled by default. + else: + config.set(module_path + [module]) diff --git a/src/migration-scripts/conntrack/3-to-4 b/src/migration-scripts/conntrack/3-to-4 old mode 100755 new mode 100644 index e90c383af..679a260d5 --- a/src/migration-scripts/conntrack/3-to-4 +++ b/src/migration-scripts/conntrack/3-to-4 @@ -1,50 +1,30 @@ -#!/usr/bin/env python3 +# Copyright 2023-2024 VyOS maintainers and contributors <maintainers@vyos.io> # -# Copyright (C) 2023 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 program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, +# 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 General Public License for more details. +# 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 General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. # Add support for IPv6 conntrack ignore, move existing nodes to `system conntrack ignore ipv4` -from sys import argv -from sys import exit - from vyos.configtree import ConfigTree -if len(argv) < 2: - print("Must specify file name!") - exit(1) - -file_name = argv[1] - -with open(file_name, 'r') as f: - config_file = f.read() - base = ['system', 'conntrack'] -config = ConfigTree(config_file) - -if not config.exists(base): - # Nothing to do - exit(0) -if config.exists(base + ['ignore', 'rule']): - config.set(base + ['ignore', 'ipv4']) - config.copy(base + ['ignore', 'rule'], base + ['ignore', 'ipv4', 'rule']) - config.delete(base + ['ignore', 'rule']) +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + # Nothing to do + return -try: - with open(file_name, 'w') as f: - f.write(config.to_string()) -except OSError as e: - print("Failed to save the modified config: {}".format(e)) - exit(1) + if config.exists(base + ['ignore', 'rule']): + config.set(base + ['ignore', 'ipv4']) + config.copy(base + ['ignore', 'rule'], base + ['ignore', 'ipv4', 'rule']) + config.delete(base + ['ignore', 'rule']) diff --git a/src/migration-scripts/conntrack/4-to-5 b/src/migration-scripts/conntrack/4-to-5 old mode 100755 new mode 100644 index d2e5fc5fa..775fe7480 --- a/src/migration-scripts/conntrack/4-to-5 +++ b/src/migration-scripts/conntrack/4-to-5 @@ -1,59 +1,39 @@ -#!/usr/bin/env python3 +# Copyright 2023-2024 VyOS maintainers and contributors <maintainers@vyos.io> # -# Copyright (C) 2023 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 program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, +# 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 General Public License for more details. +# 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 General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. # T5779: system conntrack timeout custom # Before: # Protocols tcp, udp and icmp allowed. When using udp it did not work # Only ipv4 custom timeout rules # Now: # Valid protocols are only tcp or udp. # Extend functionality to ipv6 and move ipv4 custom rules to new node: # set system conntrack timeout custom [ipv4 | ipv6] rule <rule> ... -from sys import argv -from sys import exit - from vyos.configtree import ConfigTree -if len(argv) < 2: - print("Must specify file name!") - exit(1) - -file_name = argv[1] - -with open(file_name, 'r') as f: - config_file = f.read() - base = ['system', 'conntrack'] -config = ConfigTree(config_file) - -if not config.exists(base): - # Nothing to do - exit(0) - -if config.exists(base + ['timeout', 'custom', 'rule']): - for rule in config.list_nodes(base + ['timeout', 'custom', 'rule']): - if config.exists(base + ['timeout', 'custom', 'rule', rule, 'protocol', 'tcp']): - config.set(base + ['timeout', 'custom', 'ipv4', 'rule']) - config.copy(base + ['timeout', 'custom', 'rule', rule], base + ['timeout', 'custom', 'ipv4', 'rule', rule]) - config.delete(base + ['timeout', 'custom', 'rule']) -try: - with open(file_name, 'w') as f: - f.write(config.to_string()) -except OSError as e: - print("Failed to save the modified config: {}".format(e)) - exit(1) +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + # Nothing to do + return + + if config.exists(base + ['timeout', 'custom', 'rule']): + for rule in config.list_nodes(base + ['timeout', 'custom', 'rule']): + if config.exists(base + ['timeout', 'custom', 'rule', rule, 'protocol', 'tcp']): + config.set(base + ['timeout', 'custom', 'ipv4', 'rule']) + config.copy(base + ['timeout', 'custom', 'rule', rule], base + ['timeout', 'custom', 'ipv4', 'rule', rule]) + config.delete(base + ['timeout', 'custom', 'rule']) diff --git a/src/migration-scripts/container/0-to-1 b/src/migration-scripts/container/0-to-1 old mode 100755 new mode 100644 index 6b282e082..99102a5e6 --- a/src/migration-scripts/container/0-to-1 +++ b/src/migration-scripts/container/0-to-1 @@ -1,77 +1,65 @@ -#!/usr/bin/env python3 +# Copyright 2022-2024 VyOS maintainers and contributors <maintainers@vyos.io> # -# Copyright (C) 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 program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, +# 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 General Public License for more details. +# 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 General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. # T4870: change underlaying container filesystem from vfs to overlay import os import shutil -import sys from vyos.configtree import ConfigTree from vyos.utils.process import call -if len(sys.argv) < 2: - print("Must specify file name!") - sys.exit(1) - -file_name = sys.argv[1] - -with open(file_name, 'r') as f: - config_file = f.read() - base = ['container', 'name'] -config = ConfigTree(config_file) - -# Check if containers exist and we need to perform image manipulation -if config.exists(base): - for container in config.list_nodes(base): - # Stop any given container first - call(f'sudo systemctl stop vyos-container-{container}.service') - # Export container image for later re-import to new filesystem. We store - # the backup on a real disk as a tmpfs (like /tmp) could probably lack - # memory if a host has too many containers stored. - image_name = config.return_value(base + [container, 'image']) - call(f'sudo podman image save --quiet --output /root/{container}.tar --format oci-archive {image_name}') -# No need to adjust the strage driver online (this is only used for testing and -# debugging on a live system) - it is already overlay2 when the migration script -# is run during system update. But the specified driver in the image is actually -# overwritten by the still present VFS filesystem on disk. Thus podman still -# thinks it uses VFS until we delete the libpod directory under: -# /usr/lib/live/mount/persistence/container/storage -#call('sed -i "s/vfs/overlay2/g" /etc/containers/storage.conf /usr/share/vyos/templates/container/storage.conf.j2') +def migrate(config: ConfigTree) -> None: + # Check if containers exist and we need to perform image manipulation + if config.exists(base): + for container in config.list_nodes(base): + # Stop any given container first + call(f'sudo systemctl stop vyos-container-{container}.service') + # Export container image for later re-import to new filesystem. We store + # the backup on a real disk as a tmpfs (like /tmp) could probably lack + # memory if a host has too many containers stored. + image_name = config.return_value(base + [container, 'image']) + call(f'sudo podman image save --quiet --output /root/{container}.tar --format oci-archive {image_name}') -base_path = '/usr/lib/live/mount/persistence/container/storage' -for dir in ['libpod', 'vfs', 'vfs-containers', 'vfs-images', 'vfs-layers']: - if os.path.exists(f'{base_path}/{dir}'): - shutil.rmtree(f'{base_path}/{dir}') + # No need to adjust the strage driver online (this is only used for testing and + # debugging on a live system) - it is already overlay2 when the migration script + # is run during system update. But the specified driver in the image is actually + # overwritten by the still present VFS filesystem on disk. Thus podman still + # thinks it uses VFS until we delete the libpod directory under: + # /usr/lib/live/mount/persistence/container/storage + #call('sed -i "s/vfs/overlay2/g" /etc/containers/storage.conf /usr/share/vyos/templates/container/storage.conf.j2') -# Now all remaining information about VFS is gone and we operate in overlayfs2 -# filesystem mode. Time to re-import the images. -if config.exists(base): - for container in config.list_nodes(base): - # Export container image for later re-import to new filesystem - image_name = config.return_value(base + [container, 'image']) - image_path = f'/root/{container}.tar' - call(f'sudo podman image load --quiet --input {image_path}') + base_path = '/usr/lib/live/mount/persistence/container/storage' + for dir in ['libpod', 'vfs', 'vfs-containers', 'vfs-images', 'vfs-layers']: + if os.path.exists(f'{base_path}/{dir}'): + shutil.rmtree(f'{base_path}/{dir}') - # Start any given container first - call(f'sudo systemctl start vyos-container-{container}.service') + # Now all remaining information about VFS is gone and we operate in overlayfs2 + # filesystem mode. Time to re-import the images. + if config.exists(base): + for container in config.list_nodes(base): + # Export container image for later re-import to new filesystem + image_name = config.return_value(base + [container, 'image']) + image_path = f'/root/{container}.tar' + call(f'sudo podman image load --quiet --input {image_path}') - # Delete temporary container image - if os.path.exists(image_path): - os.unlink(image_path) + # Start any given container first + call(f'sudo systemctl start vyos-container-{container}.service') + # Delete temporary container image + if os.path.exists(image_path): + os.unlink(image_path) diff --git a/src/migration-scripts/container/1-to-2 b/src/migration-scripts/container/1-to-2 old mode 100755 new mode 100644 index 408faf978..c12dd8ebb --- a/src/migration-scripts/container/1-to-2 +++ b/src/migration-scripts/container/1-to-2 @@ -1,50 +1,32 @@ -#!/usr/bin/env python3 +# Copyright 2024 VyOS maintainers and contributors <maintainers@vyos.io> # -# Copyright (C) 2024 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 program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, +# 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 General Public License for more details. +# 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 General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. # T6208: container: rename "cap-add" CLI node to "capability" -from sys import argv -from sys import exit from vyos.configtree import ConfigTree -if len(argv) < 2: - print("Must specify file name!") - exit(1) - -file_name = argv[1] - -with open(file_name, 'r') as f: - config_file = f.read() - base = ['container', 'name'] -config = ConfigTree(config_file) -# Check if containers exist and we need to perform image manipulation -if not config.exists(base): - # Nothing to do - exit(0) +def migrate(config: ConfigTree) -> None: -for container in config.list_nodes(base): - cap_path = base + [container, 'cap-add'] - if config.exists(cap_path): - config.rename(cap_path, 'capability') + # Check if containers exist and we need to perform image manipulation + if not config.exists(base): + # Nothing to do + return -try: - with open(file_name, 'w') as f: - f.write(config.to_string()) -except OSError as e: - print(f'Failed to save the modified config: {e}') - exit(1) + for container in config.list_nodes(base): + cap_path = base + [container, 'cap-add'] + if config.exists(cap_path): + config.rename(cap_path, 'capability') diff --git a/src/migration-scripts/dhcp-relay/1-to-2 b/src/migration-scripts/dhcp-relay/1-to-2 old mode 100755 new mode 100644 index 508bac6be..54cd8d6c0 --- a/src/migration-scripts/dhcp-relay/1-to-2 +++ b/src/migration-scripts/dhcp-relay/1-to-2 @@ -1,35 +1,29 @@ -#!/usr/bin/env python3 +# Copyright 2018-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# 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 <http://www.gnu.org/licenses/>. # Delete "set service dhcp-relay relay-options port" option # Delete "set service dhcpv6-relay listen-port" option -import sys - from vyos.configtree import ConfigTree -if len(sys.argv) < 2: - print("Must specify file name!") - sys.exit(1) - -file_name = sys.argv[1] - -with open(file_name, 'r') as f: - config_file = f.read() +def migrate(config: ConfigTree) -> None: + if not (config.exists(['service', 'dhcp-relay', 'relay-options', 'port']) or config.exists(['service', 'dhcpv6-relay', 'listen-port'])): + # Nothing to do + return -config = ConfigTree(config_file) - -if not (config.exists(['service', 'dhcp-relay', 'relay-options', 'port']) or config.exists(['service', 'dhcpv6-relay', 'listen-port'])): - # Nothing to do - sys.exit(0) -else: # Delete abandoned node config.delete(['service', 'dhcp-relay', 'relay-options', 'port']) # Delete abandoned node config.delete(['service', 'dhcpv6-relay', 'listen-port']) - - try: - with open(file_name, 'w') as f: - f.write(config.to_string()) - except OSError as e: - print("Failed to save the modified config: {}".format(e)) - sys.exit(1) diff --git a/src/migration-scripts/dhcp-server/10-to-11 b/src/migration-scripts/dhcp-server/10-to-11 old mode 100755 new mode 100644 index a0dc96ad0..f54a4c7b7 --- a/src/migration-scripts/dhcp-server/10-to-11 +++ b/src/migration-scripts/dhcp-server/10-to-11 @@ -1,48 +1,28 @@ -#!/usr/bin/env python3 +# Copyright 2024 VyOS maintainers and contributors <maintainers@vyos.io> # -# Copyright (C) 2024 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 program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, +# 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 General Public License for more details. +# 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 General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. # T6171: rename "service dhcp-server failover" to "service dhcp-server high-availability" -from sys import argv -from sys import exit - from vyos.configtree import ConfigTree -if len(argv) < 2: - print("Must specify file name!") - exit(1) - -file_name = argv[1] - -with open(file_name, 'r') as f: - config_file = f.read() - base = ['service', 'dhcp-server'] -config = ConfigTree(config_file) - -if not config.exists(base): - # Nothing to do - exit(0) -if config.exists(base + ['failover']): - config.rename(base + ['failover'],'high-availability') +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + # Nothing to do + return -try: - with open(file_name, 'w') as f: - f.write(config.to_string()) -except OSError as e: - print(f'Failed to save the modified config: {e}') - exit(1) \ No newline at end of file + if config.exists(base + ['failover']): + config.rename(base + ['failover'],'high-availability') diff --git a/src/migration-scripts/dhcp-server/4-to-5 b/src/migration-scripts/dhcp-server/4-to-5 old mode 100755 new mode 100644 index d15e0baf5..a655515dc --- a/src/migration-scripts/dhcp-server/4-to-5 +++ b/src/migration-scripts/dhcp-server/4-to-5 @@ -1,122 +1,118 @@ #!/usr/bin/env python3 +# Copyright 2018-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# 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 <http://www.gnu.org/licenses/>. + # Removes boolean operator from: # - "set service dhcp-server shared-network-name <xyz> subnet 172.31.0.0/24 ip-forwarding enable (true|false)" # - "set service dhcp-server shared-network-name <xyz> authoritative (true|false)" # - "set service dhcp-server disabled (true|false)" -import sys - from vyos.configtree import ConfigTree -if len(sys.argv) < 2: - print("Must specify file name!") - sys.exit(1) - -file_name = sys.argv[1] - -with open(file_name, 'r') as f: - config_file = f.read() - -config = ConfigTree(config_file) - -if not config.exists(['service', 'dhcp-server']): - # Nothing to do - sys.exit(0) -else: - base = ['service', 'dhcp-server'] - # Make node "set service dhcp-server dynamic-dns-update enable (true|false)" valueless - if config.exists(base + ['dynamic-dns-update']): - bool_val = config.return_value(base + ['dynamic-dns-update', 'enable']) - - # Delete the node with the old syntax - config.delete(base + ['dynamic-dns-update']) - if str(bool_val) == 'true': - # Enable dynamic-dns-update with new syntax - config.set(base + ['dynamic-dns-update'], value=None) - - # Make node "set service dhcp-server disabled (true|false)" valueless - if config.exists(base + ['disabled']): - bool_val = config.return_value(base + ['disabled']) - - # Delete the node with the old syntax - config.delete(base + ['disabled']) - if str(bool_val) == 'true': - # Now disable DHCP server with the new syntax - config.set(base + ['disable'], value=None) - - # Make node "set service dhcp-server hostfile-update (enable|disable) valueless - if config.exists(base + ['hostfile-update']): - bool_val = config.return_value(base + ['hostfile-update']) - - # Delete the node with the old syntax incl. all subnodes - config.delete(base + ['hostfile-update']) - if str(bool_val) == 'enable': - # Enable hostfile update with new syntax - config.set(base + ['hostfile-update'], value=None) - - # Run this for every instance if 'shared-network-name' - for network in config.list_nodes(base + ['shared-network-name']): - base_network = base + ['shared-network-name', network] - # format as tag node to avoid loading problems - config.set_tag(base + ['shared-network-name']) - - # Run this for every specified 'subnet' - for subnet in config.list_nodes(base_network + ['subnet']): - base_subnet = base_network + ['subnet', subnet] +def migrate(config: ConfigTree) -> None: + if not config.exists(['service', 'dhcp-server']): + # Nothing to do + return + else: + base = ['service', 'dhcp-server'] + # Make node "set service dhcp-server dynamic-dns-update enable (true|false)" valueless + if config.exists(base + ['dynamic-dns-update']): + bool_val = config.return_value(base + ['dynamic-dns-update', 'enable']) + + # Delete the node with the old syntax + config.delete(base + ['dynamic-dns-update']) + if str(bool_val) == 'true': + # Enable dynamic-dns-update with new syntax + config.set(base + ['dynamic-dns-update'], value=None) + + # Make node "set service dhcp-server disabled (true|false)" valueless + if config.exists(base + ['disabled']): + bool_val = config.return_value(base + ['disabled']) + + # Delete the node with the old syntax + config.delete(base + ['disabled']) + if str(bool_val) == 'true': + # Now disable DHCP server with the new syntax + config.set(base + ['disable'], value=None) + + # Make node "set service dhcp-server hostfile-update (enable|disable) valueless + if config.exists(base + ['hostfile-update']): + bool_val = config.return_value(base + ['hostfile-update']) + + # Delete the node with the old syntax incl. all subnodes + config.delete(base + ['hostfile-update']) + if str(bool_val) == 'enable': + # Enable hostfile update with new syntax + config.set(base + ['hostfile-update'], value=None) + + # Run this for every instance if 'shared-network-name' + for network in config.list_nodes(base + ['shared-network-name']): + base_network = base + ['shared-network-name', network] # format as tag node to avoid loading problems - config.set_tag(base_network + ['subnet']) + config.set_tag(base + ['shared-network-name']) - # Make node "set service dhcp-server shared-network-name <xyz> subnet 172.31.0.0/24 ip-forwarding enable" valueless - if config.exists(base_subnet + ['ip-forwarding', 'enable']): - bool_val = config.return_value(base_subnet + ['ip-forwarding', 'enable']) - # Delete the node with the old syntax - config.delete(base_subnet + ['ip-forwarding']) - if str(bool_val) == 'true': - # Recreate node with new syntax - config.set(base_subnet + ['ip-forwarding'], value=None) - - # Rename node "set service dhcp-server shared-network-name <xyz> subnet 172.31.0.0/24 start <172.16.0.4> stop <172.16.0.9> - if config.exists(base_subnet + ['start']): - # This is the new "range" id for DHCP lease ranges - r_id = 0 - for range in config.list_nodes(base_subnet + ['start']): - range_start = range - range_stop = config.return_value(base_subnet + ['start', range_start, 'stop']) + # Run this for every specified 'subnet' + for subnet in config.list_nodes(base_network + ['subnet']): + base_subnet = base_network + ['subnet', subnet] + # format as tag node to avoid loading problems + config.set_tag(base_network + ['subnet']) + # Make node "set service dhcp-server shared-network-name <xyz> subnet 172.31.0.0/24 ip-forwarding enable" valueless + if config.exists(base_subnet + ['ip-forwarding', 'enable']): + bool_val = config.return_value(base_subnet + ['ip-forwarding', 'enable']) # Delete the node with the old syntax - config.delete(base_subnet + ['start', range_start]) + config.delete(base_subnet + ['ip-forwarding']) + if str(bool_val) == 'true': + # Recreate node with new syntax + config.set(base_subnet + ['ip-forwarding'], value=None) + + # Rename node "set service dhcp-server shared-network-name <xyz> subnet 172.31.0.0/24 start <172.16.0.4> stop <172.16.0.9> + if config.exists(base_subnet + ['start']): + # This is the new "range" id for DHCP lease ranges + r_id = 0 + for range in config.list_nodes(base_subnet + ['start']): + range_start = range + range_stop = config.return_value(base_subnet + ['start', range_start, 'stop']) + + # Delete the node with the old syntax + config.delete(base_subnet + ['start', range_start]) + + # Create the node for the new syntax + # Note: range is a tag node, counter is its child, not a value + config.set(base_subnet + ['range', r_id]) + config.set(base_subnet + ['range', r_id, 'start'], value=range_start) + config.set(base_subnet + ['range', r_id, 'stop'], value=range_stop) + + # format as tag node to avoid loading problems + config.set_tag(base_subnet + ['range']) + + # increment range id for possible next range definition + r_id += 1 - # Create the node for the new syntax - # Note: range is a tag node, counter is its child, not a value - config.set(base_subnet + ['range', r_id]) - config.set(base_subnet + ['range', r_id, 'start'], value=range_start) - config.set(base_subnet + ['range', r_id, 'stop'], value=range_stop) + # Delete the node with the old syntax + config.delete(['service', 'dhcp-server', 'shared-network-name', network, 'subnet', subnet, 'start']) - # format as tag node to avoid loading problems - config.set_tag(base_subnet + ['range']) - # increment range id for possible next range definition - r_id += 1 + # Make node "set service dhcp-server shared-network-name <xyz> authoritative" valueless + if config.exists(['service', 'dhcp-server', 'shared-network-name', network, 'authoritative']): + authoritative = config.return_value(['service', 'dhcp-server', 'shared-network-name', network, 'authoritative']) # Delete the node with the old syntax - config.delete(['service', 'dhcp-server', 'shared-network-name', network, 'subnet', subnet, 'start']) + config.delete(['service', 'dhcp-server', 'shared-network-name', network, 'authoritative']) - - # Make node "set service dhcp-server shared-network-name <xyz> authoritative" valueless - if config.exists(['service', 'dhcp-server', 'shared-network-name', network, 'authoritative']): - authoritative = config.return_value(['service', 'dhcp-server', 'shared-network-name', network, 'authoritative']) - - # Delete the node with the old syntax - config.delete(['service', 'dhcp-server', 'shared-network-name', network, 'authoritative']) - - # Recreate node with new syntax - if required - if authoritative == "enable": - config.set(['service', 'dhcp-server', 'shared-network-name', network, 'authoritative']) - - try: - with open(file_name, 'w') as f: - f.write(config.to_string()) - except OSError as e: - print("Failed to save the modified config: {}".format(e)) - sys.exit(1) + # Recreate node with new syntax - if required + if authoritative == "enable": + config.set(['service', 'dhcp-server', 'shared-network-name', network, 'authoritative']) diff --git a/src/migration-scripts/dhcp-server/5-to-6 b/src/migration-scripts/dhcp-server/5-to-6 old mode 100755 new mode 100644 index f5c766a09..9404cd038 --- a/src/migration-scripts/dhcp-server/5-to-6 +++ b/src/migration-scripts/dhcp-server/5-to-6 @@ -1,87 +1,69 @@ -#!/usr/bin/env python3 +# Copyright 2021-2024 VyOS maintainers and contributors <maintainers@vyos.io> # -# Copyright (C) 2021 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 program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, +# 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 General Public License for more details. +# 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 General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. # T1968: allow multiple static-routes to be configured # T3838: rename dns-server -> name-server -import sys from vyos.configtree import ConfigTree -if len(sys.argv) < 2: - print("Must specify file name!") - sys.exit(1) - -file_name = sys.argv[1] - -with open(file_name, 'r') as f: - config_file = f.read() - base = ['service', 'dhcp-server'] -config = ConfigTree(config_file) - -if not config.exists(base + ['shared-network-name']): - # Nothing to do - exit(0) - -# Run this for every instance if 'shared-network-name' -for network in config.list_nodes(base + ['shared-network-name']): - base_network = base + ['shared-network-name', network] - - if not config.exists(base_network + ['subnet']): - continue - - # Run this for every specified 'subnet' - for subnet in config.list_nodes(base_network + ['subnet']): - base_subnet = base_network + ['subnet', subnet] - - # T1968: allow multiple static-routes to be configured - if config.exists(base_subnet + ['static-route']): - prefix = config.return_value(base_subnet + ['static-route', 'destination-subnet']) - router = config.return_value(base_subnet + ['static-route', 'router']) - config.delete(base_subnet + ['static-route']) - - config.set(base_subnet + ['static-route', prefix, 'next-hop'], value=router) - config.set_tag(base_subnet + ['static-route']) - - # T3838: rename dns-server -> name-server - if config.exists(base_subnet + ['dns-server']): - config.rename(base_subnet + ['dns-server'], 'name-server') - - - # T3672: ISC DHCP server only supports one failover peer - if config.exists(base_subnet + ['failover']): - # There can only be one failover configuration, if none is present - # we add the first one - if not config.exists(base + ['failover']): - local = config.return_value(base_subnet + ['failover', 'local-address']) - remote = config.return_value(base_subnet + ['failover', 'peer-address']) - status = config.return_value(base_subnet + ['failover', 'status']) - name = config.return_value(base_subnet + ['failover', 'name']) - - config.set(base + ['failover', 'remote'], value=remote) - config.set(base + ['failover', 'source-address'], value=local) - config.set(base + ['failover', 'status'], value=status) - config.set(base + ['failover', 'name'], value=name) - - config.delete(base_subnet + ['failover']) - config.set(base_subnet + ['enable-failover']) -try: - with open(file_name, 'w') as f: - f.write(config.to_string()) -except OSError as e: - print("Failed to save the modified config: {}".format(e)) - exit(1) +def migrate(config: ConfigTree) -> None: + if not config.exists(base + ['shared-network-name']): + # Nothing to do + return + + # Run this for every instance if 'shared-network-name' + for network in config.list_nodes(base + ['shared-network-name']): + base_network = base + ['shared-network-name', network] + + if not config.exists(base_network + ['subnet']): + continue + + # Run this for every specified 'subnet' + for subnet in config.list_nodes(base_network + ['subnet']): + base_subnet = base_network + ['subnet', subnet] + + # T1968: allow multiple static-routes to be configured + if config.exists(base_subnet + ['static-route']): + prefix = config.return_value(base_subnet + ['static-route', 'destination-subnet']) + router = config.return_value(base_subnet + ['static-route', 'router']) + config.delete(base_subnet + ['static-route']) + + config.set(base_subnet + ['static-route', prefix, 'next-hop'], value=router) + config.set_tag(base_subnet + ['static-route']) + + # T3838: rename dns-server -> name-server + if config.exists(base_subnet + ['dns-server']): + config.rename(base_subnet + ['dns-server'], 'name-server') + + + # T3672: ISC DHCP server only supports one failover peer + if config.exists(base_subnet + ['failover']): + # There can only be one failover configuration, if none is present + # we add the first one + if not config.exists(base + ['failover']): + local = config.return_value(base_subnet + ['failover', 'local-address']) + remote = config.return_value(base_subnet + ['failover', 'peer-address']) + status = config.return_value(base_subnet + ['failover', 'status']) + name = config.return_value(base_subnet + ['failover', 'name']) + + config.set(base + ['failover', 'remote'], value=remote) + config.set(base + ['failover', 'source-address'], value=local) + config.set(base + ['failover', 'status'], value=status) + config.set(base + ['failover', 'name'], value=name) + + config.delete(base_subnet + ['failover']) + config.set(base_subnet + ['enable-failover']) diff --git a/src/migration-scripts/dhcp-server/6-to-7 b/src/migration-scripts/dhcp-server/6-to-7 old mode 100755 new mode 100644 index e6c298a60..4e6583a31 --- a/src/migration-scripts/dhcp-server/6-to-7 +++ b/src/migration-scripts/dhcp-server/6-to-7 @@ -1,76 +1,58 @@ -#!/usr/bin/env python3 +# Copyright 2022-2024 VyOS maintainers and contributors <maintainers@vyos.io> # -# Copyright (C) 2024 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 program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, +# 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 General Public License for more details. +# 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 General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. # T6079: Disable duplicate static mappings -import sys from vyos.configtree import ConfigTree -if len(sys.argv) < 2: - print("Must specify file name!") - sys.exit(1) - -file_name = sys.argv[1] - -with open(file_name, 'r') as f: - config_file = f.read() - base = ['service', 'dhcp-server'] -config = ConfigTree(config_file) - -if not config.exists(base + ['shared-network-name']): - # Nothing to do - exit(0) -# Run this for every instance if 'shared-network-name' -for network in config.list_nodes(base + ['shared-network-name']): - base_network = base + ['shared-network-name', network] +def migrate(config: ConfigTree) -> None: + if not config.exists(base + ['shared-network-name']): + # Nothing to do + return - if not config.exists(base_network + ['subnet']): - continue + # Run this for every instance if 'shared-network-name' + for network in config.list_nodes(base + ['shared-network-name']): + base_network = base + ['shared-network-name', network] - for subnet in config.list_nodes(base_network + ['subnet']): - base_subnet = base_network + ['subnet', subnet] + if not config.exists(base_network + ['subnet']): + continue - if config.exists(base_subnet + ['static-mapping']): - used_mac = [] - used_ip = [] + for subnet in config.list_nodes(base_network + ['subnet']): + base_subnet = base_network + ['subnet', subnet] - for mapping in config.list_nodes(base_subnet + ['static-mapping']): - base_mapping = base_subnet + ['static-mapping', mapping] + if config.exists(base_subnet + ['static-mapping']): + used_mac = [] + used_ip = [] - if config.exists(base_mapping + ['mac-address']): - mac = config.return_value(base_mapping + ['mac-address']) + for mapping in config.list_nodes(base_subnet + ['static-mapping']): + base_mapping = base_subnet + ['static-mapping', mapping] - if mac in used_mac: - config.set(base_mapping + ['disable']) - else: - used_mac.append(mac) + if config.exists(base_mapping + ['mac-address']): + mac = config.return_value(base_mapping + ['mac-address']) - if config.exists(base_mapping + ['ip-address']): - ip = config.return_value(base_mapping + ['ip-address']) + if mac in used_mac: + config.set(base_mapping + ['disable']) + else: + used_mac.append(mac) - if ip in used_ip: - config.set(base_subnet + ['static-mapping', mapping, 'disable']) - else: - used_ip.append(ip) + if config.exists(base_mapping + ['ip-address']): + ip = config.return_value(base_mapping + ['ip-address']) -try: - with open(file_name, 'w') as f: - f.write(config.to_string()) -except OSError as e: - print("Failed to save the modified config: {}".format(e)) - exit(1) + if ip in used_ip: + config.set(base_subnet + ['static-mapping', mapping, 'disable']) + else: + used_ip.append(ip) diff --git a/src/migration-scripts/dhcp-server/7-to-8 b/src/migration-scripts/dhcp-server/7-to-8 old mode 100755 new mode 100644 index ccf385a30..7fcb62e86 --- a/src/migration-scripts/dhcp-server/7-to-8 +++ b/src/migration-scripts/dhcp-server/7-to-8 @@ -1,87 +1,69 @@ -#!/usr/bin/env python3 +# Copyright 2023-2024 VyOS maintainers and contributors <maintainers@vyos.io> # -# Copyright (C) 2023 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 program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, +# 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 General Public License for more details. +# 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 General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. # T3316: Migrate to Kea # - global-parameters will not function # - shared-network-parameters will not function # - subnet-parameters will not function # - static-mapping-parameters will not function # - host-decl-name is on by default, option removed # - ping-check no longer supported # - failover is default enabled on all subnets that exist on failover servers -import sys from vyos.configtree import ConfigTree -if (len(sys.argv) < 2): - print("Must specify file name!") - sys.exit(1) - -file_name = sys.argv[1] - -with open(file_name, 'r') as f: - config_file = f.read() - base = ['service', 'dhcp-server'] -config = ConfigTree(config_file) - -if not config.exists(base): - # Nothing to do - sys.exit(0) -if config.exists(base + ['host-decl-name']): - config.delete(base + ['host-decl-name']) +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + # Nothing to do + return -if config.exists(base + ['global-parameters']): - config.delete(base + ['global-parameters']) + if config.exists(base + ['host-decl-name']): + config.delete(base + ['host-decl-name']) -if config.exists(base + ['shared-network-name']): - for network in config.list_nodes(base + ['shared-network-name']): - base_network = base + ['shared-network-name', network] + if config.exists(base + ['global-parameters']): + config.delete(base + ['global-parameters']) - if config.exists(base_network + ['ping-check']): - config.delete(base_network + ['ping-check']) + if config.exists(base + ['shared-network-name']): + for network in config.list_nodes(base + ['shared-network-name']): + base_network = base + ['shared-network-name', network] - if config.exists(base_network + ['shared-network-parameters']): - config.delete(base_network +['shared-network-parameters']) + if config.exists(base_network + ['ping-check']): + config.delete(base_network + ['ping-check']) - if not config.exists(base_network + ['subnet']): - continue + if config.exists(base_network + ['shared-network-parameters']): + config.delete(base_network +['shared-network-parameters']) - # Run this for every specified 'subnet' - for subnet in config.list_nodes(base_network + ['subnet']): - base_subnet = base_network + ['subnet', subnet] + if not config.exists(base_network + ['subnet']): + continue - if config.exists(base_subnet + ['enable-failover']): - config.delete(base_subnet + ['enable-failover']) + # Run this for every specified 'subnet' + for subnet in config.list_nodes(base_network + ['subnet']): + base_subnet = base_network + ['subnet', subnet] - if config.exists(base_subnet + ['ping-check']): - config.delete(base_subnet + ['ping-check']) + if config.exists(base_subnet + ['enable-failover']): + config.delete(base_subnet + ['enable-failover']) - if config.exists(base_subnet + ['subnet-parameters']): - config.delete(base_subnet + ['subnet-parameters']) + if config.exists(base_subnet + ['ping-check']): + config.delete(base_subnet + ['ping-check']) - if config.exists(base_subnet + ['static-mapping']): - for mapping in config.list_nodes(base_subnet + ['static-mapping']): - if config.exists(base_subnet + ['static-mapping', mapping, 'static-mapping-parameters']): - config.delete(base_subnet + ['static-mapping', mapping, 'static-mapping-parameters']) + if config.exists(base_subnet + ['subnet-parameters']): + config.delete(base_subnet + ['subnet-parameters']) -try: - with open(file_name, 'w') as f: - f.write(config.to_string()) -except OSError as e: - print("Failed to save the modified config: {}".format(e)) - exit(1) + if config.exists(base_subnet + ['static-mapping']): + for mapping in config.list_nodes(base_subnet + ['static-mapping']): + if config.exists(base_subnet + ['static-mapping', mapping, 'static-mapping-parameters']): + config.delete(base_subnet + ['static-mapping', mapping, 'static-mapping-parameters']) diff --git a/src/migration-scripts/dhcp-server/8-to-9 b/src/migration-scripts/dhcp-server/8-to-9 old mode 100755 new mode 100644 index 151aa6d7b..5843e9fda --- a/src/migration-scripts/dhcp-server/8-to-9 +++ b/src/migration-scripts/dhcp-server/8-to-9 @@ -1,65 +1,47 @@ -#!/usr/bin/env python3 +# Copyright 2024 VyOS maintainers and contributors <maintainers@vyos.io> # -# Copyright (C) 2023 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 program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, +# 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 General Public License for more details. +# 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 General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. # T3316: # - Adjust hostname to have valid FQDN characters only (underscores aren't allowed anymore) # - Rename "service dhcp-server shared-network-name ... static-mapping <hostname> mac-address ..." # to "service dhcp-server shared-network-name ... static-mapping <hostname> mac ..." -import sys import re from vyos.configtree import ConfigTree -if len(sys.argv) < 2: - print("Must specify file name!") - sys.exit(1) - -file_name = sys.argv[1] - -with open(file_name, 'r') as f: - config_file = f.read() - base = ['service', 'dhcp-server', 'shared-network-name'] -config = ConfigTree(config_file) - -if not config.exists(base): - # Nothing to do - sys.exit(0) - -for network in config.list_nodes(base): - # Run this for every specified 'subnet' - if config.exists(base + [network, 'subnet']): - for subnet in config.list_nodes(base + [network, 'subnet']): - base_subnet = base + [network, 'subnet', subnet] - if config.exists(base_subnet + ['static-mapping']): - for hostname in config.list_nodes(base_subnet + ['static-mapping']): - base_mapping = base_subnet + ['static-mapping', hostname] - - # Rename the 'mac-address' node to 'mac' - if config.exists(base_mapping + ['mac-address']): - config.rename(base_mapping + ['mac-address'], 'mac') - - # Adjust hostname to have valid FQDN characters only - new_hostname = re.sub(r'[^a-zA-Z0-9-.]', '-', hostname) - if new_hostname != hostname: - config.rename(base_mapping, new_hostname) -try: - with open(file_name, 'w') as f: - f.write(config.to_string()) -except OSError as e: - print("Failed to save the modified config: {}".format(e)) - exit(1) +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + # Nothing to do + return + + for network in config.list_nodes(base): + # Run this for every specified 'subnet' + if config.exists(base + [network, 'subnet']): + for subnet in config.list_nodes(base + [network, 'subnet']): + base_subnet = base + [network, 'subnet', subnet] + if config.exists(base_subnet + ['static-mapping']): + for hostname in config.list_nodes(base_subnet + ['static-mapping']): + base_mapping = base_subnet + ['static-mapping', hostname] + + # Rename the 'mac-address' node to 'mac' + if config.exists(base_mapping + ['mac-address']): + config.rename(base_mapping + ['mac-address'], 'mac') + + # Adjust hostname to have valid FQDN characters only + new_hostname = re.sub(r'[^a-zA-Z0-9-.]', '-', hostname) + if new_hostname != hostname: + config.rename(base_mapping, new_hostname) diff --git a/src/migration-scripts/dhcp-server/9-to-10 b/src/migration-scripts/dhcp-server/9-to-10 old mode 100755 new mode 100644 index a459b65b5..eda97550d --- a/src/migration-scripts/dhcp-server/9-to-10 +++ b/src/migration-scripts/dhcp-server/9-to-10 @@ -1,74 +1,57 @@ -#!/usr/bin/env python3 +# Copyright 2024 VyOS maintainers and contributors <maintainers@vyos.io> # -# Copyright (C) 2024 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 program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, +# 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 General Public License for more details. +# 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 General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. # T3316: # - Migrate dhcp options under new option node # - Add subnet IDs to existing subnets -import sys from vyos.configtree import ConfigTree -if len(sys.argv) < 2: - print("Must specify file name!") - sys.exit(1) - -file_name = sys.argv[1] - -with open(file_name, 'r') as f: - config_file = f.read() - base = ['service', 'dhcp-server', 'shared-network-name'] -config = ConfigTree(config_file) - -if not config.exists(base): - # Nothing to do - sys.exit(0) option_nodes = ['bootfile-name', 'bootfile-server', 'bootfile-size', 'captive-portal', 'client-prefix-length', 'default-router', 'domain-name', 'domain-search', 'name-server', 'ip-forwarding', 'ipv6-only-preferred', 'ntp-server', 'pop-server', 'server-identifier', 'smtp-server', 'static-route', 'tftp-server-name', 'time-offset', 'time-server', 'time-zone', 'vendor-option', 'wins-server', 'wpad-url'] -subnet_id = 1 -for network in config.list_nodes(base): - for option in option_nodes: - if config.exists(base + [network, option]): - config.set(base + [network, 'option']) - config.copy(base + [network, option], base + [network, 'option', option]) - config.delete(base + [network, option]) +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + # Nothing to do + return + + subnet_id = 1 - if config.exists(base + [network, 'subnet']): - for subnet in config.list_nodes(base + [network, 'subnet']): - base_subnet = base + [network, 'subnet', subnet] + for network in config.list_nodes(base): + for option in option_nodes: + if config.exists(base + [network, option]): + config.set(base + [network, 'option']) + config.copy(base + [network, option], base + [network, 'option', option]) + config.delete(base + [network, option]) - for option in option_nodes: - if config.exists(base_subnet + [option]): - config.set(base_subnet + ['option']) - config.copy(base_subnet + [option], base_subnet + ['option', option]) - config.delete(base_subnet + [option]) + if config.exists(base + [network, 'subnet']): + for subnet in config.list_nodes(base + [network, 'subnet']): + base_subnet = base + [network, 'subnet', subnet] - config.set(base_subnet + ['subnet-id'], value=subnet_id) - subnet_id += 1 + for option in option_nodes: + if config.exists(base_subnet + [option]): + config.set(base_subnet + ['option']) + config.copy(base_subnet + [option], base_subnet + ['option', option]) + config.delete(base_subnet + [option]) -try: - with open(file_name, 'w') as f: - f.write(config.to_string()) -except OSError as e: - print("Failed to save the modified config: {}".format(e)) - exit(1) + config.set(base_subnet + ['subnet-id'], value=subnet_id) + subnet_id += 1 diff --git a/src/migration-scripts/dhcpv6-server/0-to-1 b/src/migration-scripts/dhcpv6-server/0-to-1 old mode 100755 new mode 100644 index deae1ca29..fd9b2d739 --- a/src/migration-scripts/dhcpv6-server/0-to-1 +++ b/src/migration-scripts/dhcpv6-server/0-to-1 @@ -1,61 +1,44 @@ -#!/usr/bin/env python3 +# Copyright 202-2024 VyOS maintainers and contributors <maintainers@vyos.io> # -# Copyright (C) 2020 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 program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, +# 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 General Public License for more details. +# 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 General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. # combine both sip-server-address and sip-server-name nodes to common sip-server -from sys import argv, exit from vyos.configtree import ConfigTree -if len(argv) < 2: - print("Must specify file name!") - exit(1) - -file_name = argv[1] +base = ['service', 'dhcpv6-server', 'shared-network-name'] -with open(file_name, 'r') as f: - config_file = f.read() +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + # Nothing to do + return -config = ConfigTree(config_file) -base = ['service', 'dhcpv6-server', 'shared-network-name'] -if not config.exists(base): - # Nothing to do - exit(0) -else: # we need to run this for every configured network for network in config.list_nodes(base): for subnet in config.list_nodes(base + [network, 'subnet']): sip_server = [] # Do we have 'sip-server-address' configured? if config.exists(base + [network, 'subnet', subnet, 'sip-server-address']): sip_server += config.return_values(base + [network, 'subnet', subnet, 'sip-server-address']) config.delete(base + [network, 'subnet', subnet, 'sip-server-address']) # Do we have 'sip-server-name' configured? if config.exists(base + [network, 'subnet', subnet, 'sip-server-name']): sip_server += config.return_values(base + [network, 'subnet', subnet, 'sip-server-name']) config.delete(base + [network, 'subnet', subnet, 'sip-server-name']) # Write new CLI value for sip-server for server in sip_server: config.set(base + [network, 'subnet', subnet, 'sip-server'], value=server, replace=False) - - try: - with open(file_name, 'w') as f: - f.write(config.to_string()) - except OSError as e: - print("Failed to save the modified config: {}".format(e)) - exit(1) diff --git a/src/migration-scripts/dhcpv6-server/1-to-2 b/src/migration-scripts/dhcpv6-server/1-to-2 old mode 100755 new mode 100644 index cc5a8900a..ad307495c --- a/src/migration-scripts/dhcpv6-server/1-to-2 +++ b/src/migration-scripts/dhcpv6-server/1-to-2 @@ -1,86 +1,68 @@ -#!/usr/bin/env python3 +# Copyright 2023-2024 VyOS maintainers and contributors <maintainers@vyos.io> # -# Copyright (C) 2023 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 program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, +# 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 General Public License for more details. +# 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 General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. # T3316: Migrate to Kea # - Kea was meant to have support for key "prefix-highest" under PD which would allow an address range # However this seems to have never been implemented. A conversion to prefix length is needed (where possible). # Ref: https://lists.isc.org/pipermail/kea-users/2022-November/003686.html # - Remove prefix temporary value, convert to multi leafNode (https://kea.readthedocs.io/en/kea-2.2.0/arm/dhcp6-srv.html#dhcpv6-server-limitations) -import sys from vyos.configtree import ConfigTree from vyos.utils.network import ipv6_prefix_length -if (len(sys.argv) < 1): - print("Must specify file name!") - sys.exit(1) - -file_name = sys.argv[1] - -with open(file_name, 'r') as f: - config_file = f.read() - base = ['service', 'dhcpv6-server', 'shared-network-name'] -config = ConfigTree(config_file) - -if not config.exists(base): - # Nothing to do - exit(0) -for network in config.list_nodes(base): - if not config.exists(base + [network, 'subnet']): - continue +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + # Nothing to do + return - for subnet in config.list_nodes(base + [network, 'subnet']): - # Delete temporary value under address-range prefix, convert tagNode to leafNode multi - if config.exists(base + [network, 'subnet', subnet, 'address-range', 'prefix']): - prefix_base = base + [network, 'subnet', subnet, 'address-range', 'prefix'] - prefixes = config.list_nodes(prefix_base) - - config.delete(prefix_base) + for network in config.list_nodes(base): + if not config.exists(base + [network, 'subnet']): + continue - for prefix in prefixes: - config.set(prefix_base, value=prefix, replace=False) + for subnet in config.list_nodes(base + [network, 'subnet']): + # Delete temporary value under address-range prefix, convert tagNode to leafNode multi + if config.exists(base + [network, 'subnet', subnet, 'address-range', 'prefix']): + prefix_base = base + [network, 'subnet', subnet, 'address-range', 'prefix'] + prefixes = config.list_nodes(prefix_base) + + config.delete(prefix_base) - if config.exists(base + [network, 'subnet', subnet, 'prefix-delegation', 'prefix']): - prefix_base = base + [network, 'subnet', subnet, 'prefix-delegation', 'prefix'] + for prefix in prefixes: + config.set(prefix_base, value=prefix, replace=False) - config.set(prefix_base) - config.set_tag(prefix_base) + if config.exists(base + [network, 'subnet', subnet, 'prefix-delegation', 'prefix']): + prefix_base = base + [network, 'subnet', subnet, 'prefix-delegation', 'prefix'] - for start in config.list_nodes(base + [network, 'subnet', subnet, 'prefix-delegation', 'start']): - path = base + [network, 'subnet', subnet, 'prefix-delegation', 'start', start] + config.set(prefix_base) + config.set_tag(prefix_base) - delegated_length = config.return_value(path + ['prefix-length']) - stop = config.return_value(path + ['stop']) + for start in config.list_nodes(base + [network, 'subnet', subnet, 'prefix-delegation', 'start']): + path = base + [network, 'subnet', subnet, 'prefix-delegation', 'start', start] - prefix_length = ipv6_prefix_length(start, stop) + delegated_length = config.return_value(path + ['prefix-length']) + stop = config.return_value(path + ['stop']) - # This range could not be converted into a simple prefix length and must be skipped - if not prefix_length: - continue + prefix_length = ipv6_prefix_length(start, stop) - config.set(prefix_base + [start, 'delegated-length'], value=delegated_length) - config.set(prefix_base + [start, 'prefix-length'], value=prefix_length) + # This range could not be converted into a simple prefix length and must be skipped + if not prefix_length: + continue - config.delete(base + [network, 'subnet', subnet, 'prefix-delegation', 'start']) + config.set(prefix_base + [start, 'delegated-length'], value=delegated_length) + config.set(prefix_base + [start, 'prefix-length'], value=prefix_length) -try: - with open(file_name, 'w') as f: - f.write(config.to_string()) -except OSError as e: - print("Failed to save the modified config: {}".format(e)) - exit(1) + config.delete(base + [network, 'subnet', subnet, 'prefix-delegation', 'start']) diff --git a/src/migration-scripts/dhcpv6-server/2-to-3 b/src/migration-scripts/dhcpv6-server/2-to-3 old mode 100755 new mode 100644 index f4bdc1d1e..b44798d18 --- a/src/migration-scripts/dhcpv6-server/2-to-3 +++ b/src/migration-scripts/dhcpv6-server/2-to-3 @@ -1,78 +1,60 @@ -#!/usr/bin/env python3 +# Copyright 2023-2024 VyOS maintainers and contributors <maintainers@vyos.io> # -# Copyright (C) 2023 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 program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, +# 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 General Public License for more details. +# 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 General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. # T3316: # - Adjust hostname to have valid FQDN characters only (underscores aren't allowed anymore) # - Adjust duid (old identifier) to comply with duid format # - Rename "service dhcpv6-server shared-network-name ... static-mapping <hostname> identifier ..." # to "service dhcpv6-server shared-network-name ... static-mapping <hostname> duid ..." # - Rename "service dhcpv6-server shared-network-name ... static-mapping <hostname> mac-address ..." # to "service dhcpv6-server shared-network-name ... static-mapping <hostname> mac ..." -import sys import re from vyos.configtree import ConfigTree -if len(sys.argv) < 2: - print("Must specify file name!") - sys.exit(1) - -file_name = sys.argv[1] - -with open(file_name, 'r') as f: - config_file = f.read() - base = ['service', 'dhcpv6-server', 'shared-network-name'] -config = ConfigTree(config_file) - -if not config.exists(base): - # Nothing to do - sys.exit(0) - -for network in config.list_nodes(base): - # Run this for every specified 'subnet' - if config.exists(base + [network, 'subnet']): - for subnet in config.list_nodes(base + [network, 'subnet']): - base_subnet = base + [network, 'subnet', subnet] - if config.exists(base_subnet + ['static-mapping']): - for hostname in config.list_nodes(base_subnet + ['static-mapping']): - base_mapping = base_subnet + ['static-mapping', hostname] - if config.exists(base_mapping + ['identifier']): - - # Adjust duid to comply with duid format (a:3:b:04:... => 0a:03:0b:04:...) - duid = config.return_value(base_mapping + ['identifier']) - new_duid = ':'.join(x.rjust(2,'0') for x in duid.split(':')) - if new_duid != duid: - config.set(base_mapping + ['identifier'], new_duid) - - # Rename the 'identifier' node to 'duid' - config.rename(base_mapping + ['identifier'], 'duid') - - # Rename the 'mac-address' node to 'mac' - if config.exists(base_mapping + ['mac-address']): - config.rename(base_mapping + ['mac-address'], 'mac') - - # Adjust hostname to have valid FQDN characters only - new_hostname = re.sub(r'[^a-zA-Z0-9-.]', '-', hostname) - if new_hostname != hostname: - config.rename(base_mapping, new_hostname) -try: - with open(file_name, 'w') as f: - f.write(config.to_string()) -except OSError as e: - print("Failed to save the modified config: {}".format(e)) - exit(1) +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + # Nothing to do + return + + for network in config.list_nodes(base): + # Run this for every specified 'subnet' + if config.exists(base + [network, 'subnet']): + for subnet in config.list_nodes(base + [network, 'subnet']): + base_subnet = base + [network, 'subnet', subnet] + if config.exists(base_subnet + ['static-mapping']): + for hostname in config.list_nodes(base_subnet + ['static-mapping']): + base_mapping = base_subnet + ['static-mapping', hostname] + if config.exists(base_mapping + ['identifier']): + + # Adjust duid to comply with duid format (a:3:b:04:... => 0a:03:0b:04:...) + duid = config.return_value(base_mapping + ['identifier']) + new_duid = ':'.join(x.rjust(2,'0') for x in duid.split(':')) + if new_duid != duid: + config.set(base_mapping + ['identifier'], new_duid) + + # Rename the 'identifier' node to 'duid' + config.rename(base_mapping + ['identifier'], 'duid') + + # Rename the 'mac-address' node to 'mac' + if config.exists(base_mapping + ['mac-address']): + config.rename(base_mapping + ['mac-address'], 'mac') + + # Adjust hostname to have valid FQDN characters only + new_hostname = re.sub(r'[^a-zA-Z0-9-.]', '-', hostname) + if new_hostname != hostname: + config.rename(base_mapping, new_hostname) diff --git a/src/migration-scripts/dhcpv6-server/3-to-4 b/src/migration-scripts/dhcpv6-server/3-to-4 old mode 100755 new mode 100644 index 7efc492a5..e38e36505 --- a/src/migration-scripts/dhcpv6-server/3-to-4 +++ b/src/migration-scripts/dhcpv6-server/3-to-4 @@ -1,89 +1,72 @@ -#!/usr/bin/env python3 +# Copyright 2024 VyOS maintainers and contributors <maintainers@vyos.io> # -# Copyright (C) 2024 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 program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, +# 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 General Public License for more details. +# 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 General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. # T3316: # - Add subnet IDs to existing subnets # - Move options to option node # - Migrate address-range to range tagNode -import sys from vyos.configtree import ConfigTree -if len(sys.argv) < 2: - print("Must specify file name!") - sys.exit(1) - -file_name = sys.argv[1] - -with open(file_name, 'r') as f: - config_file = f.read() - base = ['service', 'dhcpv6-server', 'shared-network-name'] -config = ConfigTree(config_file) - -if not config.exists(base): - # Nothing to do - sys.exit(0) option_nodes = ['captive-portal', 'domain-search', 'name-server', 'nis-domain', 'nis-server', 'nisplus-domain', 'nisplus-server', 'sip-server', 'sntp-server', 'vendor-option'] -subnet_id = 1 -for network in config.list_nodes(base): - if config.exists(base + [network, 'subnet']): - for subnet in config.list_nodes(base + [network, 'subnet']): - base_subnet = base + [network, 'subnet', subnet] +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + # Nothing to do + return + + subnet_id = 1 - if config.exists(base_subnet + ['address-range']): - config.set(base_subnet + ['range']) - config.set_tag(base_subnet + ['range']) + for network in config.list_nodes(base): + if config.exists(base + [network, 'subnet']): + for subnet in config.list_nodes(base + [network, 'subnet']): + base_subnet = base + [network, 'subnet', subnet] - range_id = 1 + if config.exists(base_subnet + ['address-range']): + config.set(base_subnet + ['range']) + config.set_tag(base_subnet + ['range']) - if config.exists(base_subnet + ['address-range', 'prefix']): - for prefix in config.return_values(base_subnet + ['address-range', 'prefix']): - config.set(base_subnet + ['range', range_id, 'prefix'], value=prefix) + range_id = 1 - range_id += 1 + if config.exists(base_subnet + ['address-range', 'prefix']): + for prefix in config.return_values(base_subnet + ['address-range', 'prefix']): + config.set(base_subnet + ['range', range_id, 'prefix'], value=prefix) - if config.exists(base_subnet + ['address-range', 'start']): - for start in config.list_nodes(base_subnet + ['address-range', 'start']): - stop = config.return_value(base_subnet + ['address-range', 'start', start, 'stop']) + range_id += 1 - config.set(base_subnet + ['range', range_id, 'start'], value=start) - config.set(base_subnet + ['range', range_id, 'stop'], value=stop) + if config.exists(base_subnet + ['address-range', 'start']): + for start in config.list_nodes(base_subnet + ['address-range', 'start']): + stop = config.return_value(base_subnet + ['address-range', 'start', start, 'stop']) - range_id += 1 + config.set(base_subnet + ['range', range_id, 'start'], value=start) + config.set(base_subnet + ['range', range_id, 'stop'], value=stop) - config.delete(base_subnet + ['address-range']) + range_id += 1 - for option in option_nodes: - if config.exists(base_subnet + [option]): - config.set(base_subnet + ['option']) - config.copy(base_subnet + [option], base_subnet + ['option', option]) - config.delete(base_subnet + [option]) + config.delete(base_subnet + ['address-range']) - config.set(base_subnet + ['subnet-id'], value=subnet_id) - subnet_id += 1 + for option in option_nodes: + if config.exists(base_subnet + [option]): + config.set(base_subnet + ['option']) + config.copy(base_subnet + [option], base_subnet + ['option', option]) + config.delete(base_subnet + [option]) -try: - with open(file_name, 'w') as f: - f.write(config.to_string()) -except OSError as e: - print("Failed to save the modified config: {}".format(e)) - exit(1) + config.set(base_subnet + ['subnet-id'], value=subnet_id) + subnet_id += 1 diff --git a/src/migration-scripts/dhcpv6-server/4-to-5 b/src/migration-scripts/dhcpv6-server/4-to-5 old mode 100755 new mode 100644 index 55fda91b3..ad18e1a84 --- a/src/migration-scripts/dhcpv6-server/4-to-5 +++ b/src/migration-scripts/dhcpv6-server/4-to-5 @@ -1,91 +1,73 @@ -#!/usr/bin/env python3 +# Copyright 2024 VyOS maintainers and contributors <maintainers@vyos.io> # -# Copyright (C) 2024 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 program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, +# 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 General Public License for more details. +# 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 General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. # T5993: Check if subnet is locally accessible and assign interface to subnet -import sys from ipaddress import ip_network from vyos.configtree import ConfigTree -if (len(sys.argv) < 1): - print("Must specify file name!") - sys.exit(1) - -file_name = sys.argv[1] - -with open(file_name, 'r') as f: - config_file = f.read() - base = ['service', 'dhcpv6-server', 'shared-network-name'] -config = ConfigTree(config_file) - -if not config.exists(base): - # Nothing to do - exit(0) - -def find_subnet_interface(subnet): - subnet_net = ip_network(subnet) - - def check_addr(if_path): - if config.exists(if_path + ['address']): - for addr in config.return_values(if_path + ['address']): - try: - if ip_network(addr, strict=False) == subnet_net: - return True - except: - pass # interface address was probably "dhcp" or other magic string - return None - - for iftype in config.list_nodes(['interfaces']): - for ifname in config.list_nodes(['interfaces', iftype]): - if_base = ['interfaces', iftype, ifname] - - if check_addr(if_base): - return ifname - - if config.exists(if_base + ['vif']): - for vif in config.list_nodes(if_base + ['vif']): - if check_addr(if_base + ['vif', vif]): - return f'{ifname}.{vif}' - - if config.exists(if_base + ['vif-s']): - for vifs in config.list_nodes(if_base + ['vif-s']): - if check_addr(if_base + ['vif-s', vifs]): - return f'{ifname}.{vifs}' - - if config.exists(if_base + ['vif-s', vifs, 'vif-c']): - for vifc in config.list_nodes(if_base + ['vif-s', vifs, 'vif-c']): - if check_addr(if_base + ['vif-s', vifs, 'vif-c', vifc]): - return f'{ifname}.{vifs}.{vifc}' - - return False - -for network in config.list_nodes(base): - if not config.exists(base + [network, 'subnet']): - continue - - for subnet in config.list_nodes(base + [network, 'subnet']): - subnet_interface = find_subnet_interface(subnet) - - if subnet_interface: - config.set(base + [network, 'subnet', subnet, 'interface'], value=subnet_interface) -try: - with open(file_name, 'w') as f: - f.write(config.to_string()) -except OSError as e: - print("Failed to save the modified config: {}".format(e)) - exit(1) +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + # Nothing to do + return + + def find_subnet_interface(subnet): + subnet_net = ip_network(subnet) + + def check_addr(if_path): + if config.exists(if_path + ['address']): + for addr in config.return_values(if_path + ['address']): + try: + if ip_network(addr, strict=False) == subnet_net: + return True + except: + pass # interface address was probably "dhcp" or other magic string + return None + + for iftype in config.list_nodes(['interfaces']): + for ifname in config.list_nodes(['interfaces', iftype]): + if_base = ['interfaces', iftype, ifname] + + if check_addr(if_base): + return ifname + + if config.exists(if_base + ['vif']): + for vif in config.list_nodes(if_base + ['vif']): + if check_addr(if_base + ['vif', vif]): + return f'{ifname}.{vif}' + + if config.exists(if_base + ['vif-s']): + for vifs in config.list_nodes(if_base + ['vif-s']): + if check_addr(if_base + ['vif-s', vifs]): + return f'{ifname}.{vifs}' + + if config.exists(if_base + ['vif-s', vifs, 'vif-c']): + for vifc in config.list_nodes(if_base + ['vif-s', vifs, 'vif-c']): + if check_addr(if_base + ['vif-s', vifs, 'vif-c', vifc]): + return f'{ifname}.{vifs}.{vifc}' + + return False + + for network in config.list_nodes(base): + if not config.exists(base + [network, 'subnet']): + continue + + for subnet in config.list_nodes(base + [network, 'subnet']): + subnet_interface = find_subnet_interface(subnet) + + if subnet_interface: + config.set(base + [network, 'subnet', subnet, 'interface'], value=subnet_interface) diff --git a/src/migration-scripts/dns-dynamic/0-to-1 b/src/migration-scripts/dns-dynamic/0-to-1 old mode 100755 new mode 100644 index b7674a9c8..6a91b36af --- a/src/migration-scripts/dns-dynamic/0-to-1 +++ b/src/migration-scripts/dns-dynamic/0-to-1 @@ -1,128 +1,109 @@ -#!/usr/bin/env python3 - -# Copyright (C) 2023 VyOS maintainers and contributors +# Copyright 2023-2024 VyOS maintainers and contributors <maintainers@vyos.io> # -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. +# 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 program is distributed in the hope that it will be useful, +# 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 General Public License for more details. +# 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 General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. # T5144: # - migrate "service dns dynamic interface ..." # to "service dns dynamic address ..." # - migrate "service dns dynamic interface <interface> use-web ..." # to "service dns dynamic address <address> web-options ..." # - migrate "service dns dynamic interface <interface> rfc2136 <config> record ..." # to "service dns dynamic address <address> rfc2136 <config> host-name ..." # - migrate "service dns dynamic interface <interface> service <config> login ..." # to "service dns dynamic address <address> service <config> username ..." # - apply global 'ipv6-enable' to per <config> 'ip-version: ipv6' # - apply service protocol mapping upfront, they are not 'auto-detected' anymore # - migrate web-options url to stricter format -import sys import re from vyos.configtree import ConfigTree service_protocol_mapping = { 'afraid': 'freedns', 'changeip': 'changeip', 'cloudflare': 'cloudflare', 'dnspark': 'dnspark', 'dslreports': 'dslreports1', 'dyndns': 'dyndns2', 'easydns': 'easydns', 'namecheap': 'namecheap', 'noip': 'noip', 'sitelutions': 'sitelutions', 'zoneedit': 'zoneedit1' } -if len(sys.argv) < 2: - print("Must specify file name!") - sys.exit(1) - -file_name = sys.argv[1] - -with open(file_name, 'r') as f: - config_file = f.read() - -config = ConfigTree(config_file) - old_base_path = ['service', 'dns', 'dynamic', 'interface'] new_base_path = ['service', 'dns', 'dynamic', 'address'] -if not config.exists(old_base_path): - # Nothing to do - sys.exit(0) - -# Migrate "service dns dynamic interface" -# to "service dns dynamic address" -config.rename(old_base_path, new_base_path[-1]) - -for address in config.list_nodes(new_base_path): - # Migrate "service dns dynamic interface <interface> rfc2136 <config> record" - # to "service dns dynamic address <address> rfc2136 <config> host-name" - if config.exists(new_base_path + [address, 'rfc2136']): - for rfc_cfg in config.list_nodes(new_base_path + [address, 'rfc2136']): - if config.exists(new_base_path + [address, 'rfc2136', rfc_cfg, 'record']): - config.rename(new_base_path + [address, 'rfc2136', rfc_cfg, 'record'], 'host-name') - - # Migrate "service dns dynamic interface <interface> service <config> login" - # to "service dns dynamic address <address> service <config> username" - if config.exists(new_base_path + [address, 'service']): - for svc_cfg in config.list_nodes(new_base_path + [address, 'service']): - if config.exists(new_base_path + [address, 'service', svc_cfg, 'login']): - config.rename(new_base_path + [address, 'service', svc_cfg, 'login'], 'username') - # Apply global 'ipv6-enable' to per <config> 'ip-version: ipv6' - if config.exists(new_base_path + [address, 'ipv6-enable']): - config.set(new_base_path + [address, 'service', svc_cfg, 'ip-version'], 'ipv6') - config.delete(new_base_path + [address, 'ipv6-enable']) - # Apply service protocol mapping upfront, they are not 'auto-detected' anymore - if svc_cfg in service_protocol_mapping: - config.set(new_base_path + [address, 'service', svc_cfg, 'protocol'], - service_protocol_mapping.get(svc_cfg)) - - # If use-web is set, then: - # Move "service dns dynamic address <address> <service|rfc2136> <service> ..." - # to "service dns dynamic address web <service|rfc2136> <service>-<address> ..." - # Move "service dns dynamic address web use-web ..." - # to "service dns dynamic address web web-options ..." - # Note: The config is named <service>-<address> to avoid name conflict with old entries - if config.exists(new_base_path + [address, 'use-web']): - for svc_type in ['rfc2136', 'service']: - if config.exists(new_base_path + [address, svc_type]): - config.set(new_base_path + ['web', svc_type]) - config.set_tag(new_base_path + ['web', svc_type]) - for svc_cfg in config.list_nodes(new_base_path + [address, svc_type]): - config.copy(new_base_path + [address, svc_type, svc_cfg], - new_base_path + ['web', svc_type, f'{svc_cfg}-{address}']) - - # Multiple web-options were not supported, so copy only the first one - # Also, migrate web-options url to stricter format and transition - # checkip.dyndns.org to https://domains.google.com/checkip for better - # TLS support (see: https://github.com/ddclient/ddclient/issues/597) - if not config.exists(new_base_path + ['web', 'web-options']): - config.copy(new_base_path + [address, 'use-web'], new_base_path + ['web', 'web-options']) - if config.exists(new_base_path + ['web', 'web-options', 'url']): - url = config.return_value(new_base_path + ['web', 'web-options', 'url']) - if re.search("^(https?://)?checkip\.dyndns\.org", url): - config.set(new_base_path + ['web', 'web-options', 'url'], 'https://domains.google.com/checkip') - if not url.startswith(('http://', 'https://')): - config.set(new_base_path + ['web', 'web-options', 'url'], f'https://{url}') - - config.delete(new_base_path + [address]) - -try: - with open(file_name, 'w') as f: - f.write(config.to_string()) -except OSError as e: - print("Failed to save the modified config: {}".format(e)) - sys.exit(1) +def migrate(config: ConfigTree) -> None: + if not config.exists(old_base_path): + # Nothing to do + return + + # Migrate "service dns dynamic interface" + # to "service dns dynamic address" + config.rename(old_base_path, new_base_path[-1]) + + for address in config.list_nodes(new_base_path): + # Migrate "service dns dynamic interface <interface> rfc2136 <config> record" + # to "service dns dynamic address <address> rfc2136 <config> host-name" + if config.exists(new_base_path + [address, 'rfc2136']): + for rfc_cfg in config.list_nodes(new_base_path + [address, 'rfc2136']): + if config.exists(new_base_path + [address, 'rfc2136', rfc_cfg, 'record']): + config.rename(new_base_path + [address, 'rfc2136', rfc_cfg, 'record'], 'host-name') + + # Migrate "service dns dynamic interface <interface> service <config> login" + # to "service dns dynamic address <address> service <config> username" + if config.exists(new_base_path + [address, 'service']): + for svc_cfg in config.list_nodes(new_base_path + [address, 'service']): + if config.exists(new_base_path + [address, 'service', svc_cfg, 'login']): + config.rename(new_base_path + [address, 'service', svc_cfg, 'login'], 'username') + # Apply global 'ipv6-enable' to per <config> 'ip-version: ipv6' + if config.exists(new_base_path + [address, 'ipv6-enable']): + config.set(new_base_path + [address, 'service', svc_cfg, 'ip-version'], 'ipv6') + config.delete(new_base_path + [address, 'ipv6-enable']) + # Apply service protocol mapping upfront, they are not 'auto-detected' anymore + if svc_cfg in service_protocol_mapping: + config.set(new_base_path + [address, 'service', svc_cfg, 'protocol'], + service_protocol_mapping.get(svc_cfg)) + + # If use-web is set, then: + # Move "service dns dynamic address <address> <service|rfc2136> <service> ..." + # to "service dns dynamic address web <service|rfc2136> <service>-<address> ..." + # Move "service dns dynamic address web use-web ..." + # to "service dns dynamic address web web-options ..." + # Note: The config is named <service>-<address> to avoid name conflict with old entries + if config.exists(new_base_path + [address, 'use-web']): + for svc_type in ['rfc2136', 'service']: + if config.exists(new_base_path + [address, svc_type]): + config.set(new_base_path + ['web', svc_type]) + config.set_tag(new_base_path + ['web', svc_type]) + for svc_cfg in config.list_nodes(new_base_path + [address, svc_type]): + config.copy(new_base_path + [address, svc_type, svc_cfg], + new_base_path + ['web', svc_type, f'{svc_cfg}-{address}']) + + # Multiple web-options were not supported, so copy only the first one + # Also, migrate web-options url to stricter format and transition + # checkip.dyndns.org to https://domains.google.com/checkip for better + # TLS support (see: https://github.com/ddclient/ddclient/issues/597) + if not config.exists(new_base_path + ['web', 'web-options']): + config.copy(new_base_path + [address, 'use-web'], new_base_path + ['web', 'web-options']) + if config.exists(new_base_path + ['web', 'web-options', 'url']): + url = config.return_value(new_base_path + ['web', 'web-options', 'url']) + if re.search("^(https?://)?checkip\.dyndns\.org", url): + config.set(new_base_path + ['web', 'web-options', 'url'], 'https://domains.google.com/checkip') + if not url.startswith(('http://', 'https://')): + config.set(new_base_path + ['web', 'web-options', 'url'], f'https://{url}') + + config.delete(new_base_path + [address]) diff --git a/src/migration-scripts/dns-dynamic/1-to-2 b/src/migration-scripts/dns-dynamic/1-to-2 old mode 100755 new mode 100644 index 8b599b57a..5dca9e32f --- a/src/migration-scripts/dns-dynamic/1-to-2 +++ b/src/migration-scripts/dns-dynamic/1-to-2 @@ -1,70 +1,51 @@ -#!/usr/bin/env python3 - -# Copyright (C) 2023 VyOS maintainers and contributors +# Copyright 2023-2024 VyOS maintainers and contributors <maintainers@vyos.io> # -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. +# 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 program is distributed in the hope that it will be useful, +# 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 General Public License for more details. +# 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 General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. # T5708: # - migrate "service dns dynamic timeout ..." # to "service dns dynamic interval ..." # - remove "service dns dynamic address <interface> web-options ..." when <interface> != "web" # - migrate "service dns dynamic address <interface> service <service> protocol dnsexit" # to "service dns dynamic address <interface> service <service> protocol dnsexit2" -import sys from vyos.configtree import ConfigTree -if len(sys.argv) < 2: - print("Must specify file name!") - sys.exit(1) - -file_name = sys.argv[1] - -with open(file_name, 'r') as f: - config_file = f.read() - -config = ConfigTree(config_file) - base_path = ['service', 'dns', 'dynamic'] timeout_path = base_path + ['timeout'] address_path = base_path + ['address'] -if not config.exists(base_path): - # Nothing to do - sys.exit(0) - -# Migrate "service dns dynamic timeout ..." -# to "service dns dynamic interval ..." -if config.exists(timeout_path): - config.rename(timeout_path, 'interval') - -# Remove "service dns dynamic address <interface> web-options ..." when <interface> != "web" -for address in config.list_nodes(address_path): - if config.exists(address_path + [address, 'web-options']) and address != 'web': - config.delete(address_path + [address, 'web-options']) - -# Migrate "service dns dynamic address <interface> service <service> protocol dnsexit" -# to "service dns dynamic address <interface> service <service> protocol dnsexit2" -for address in config.list_nodes(address_path): - for svc_cfg in config.list_nodes(address_path + [address, 'service']): - if config.exists(address_path + [address, 'service', svc_cfg, 'protocol']): - protocol = config.return_value(address_path + [address, 'service', svc_cfg, 'protocol']) - if protocol == 'dnsexit': - config.set(address_path + [address, 'service', svc_cfg, 'protocol'], 'dnsexit2') - -try: - with open(file_name, 'w') as f: - f.write(config.to_string()) -except OSError as e: - print("Failed to save the modified config: {}".format(e)) - sys.exit(1) +def migrate(config: ConfigTree) -> None: + if not config.exists(base_path): + # Nothing to do + return + + # Migrate "service dns dynamic timeout ..." + # to "service dns dynamic interval ..." + if config.exists(timeout_path): + config.rename(timeout_path, 'interval') + + # Remove "service dns dynamic address <interface> web-options ..." when <interface> != "web" + for address in config.list_nodes(address_path): + if config.exists(address_path + [address, 'web-options']) and address != 'web': + config.delete(address_path + [address, 'web-options']) + + # Migrate "service dns dynamic address <interface> service <service> protocol dnsexit" + # to "service dns dynamic address <interface> service <service> protocol dnsexit2" + for address in config.list_nodes(address_path): + for svc_cfg in config.list_nodes(address_path + [address, 'service']): + if config.exists(address_path + [address, 'service', svc_cfg, 'protocol']): + protocol = config.return_value(address_path + [address, 'service', svc_cfg, 'protocol']) + if protocol == 'dnsexit': + config.set(address_path + [address, 'service', svc_cfg, 'protocol'], 'dnsexit2') diff --git a/src/migration-scripts/dns-dynamic/2-to-3 b/src/migration-scripts/dns-dynamic/2-to-3 old mode 100755 new mode 100644 index 4e0aa37d5..9aafc41a4 --- a/src/migration-scripts/dns-dynamic/2-to-3 +++ b/src/migration-scripts/dns-dynamic/2-to-3 @@ -1,119 +1,99 @@ -#!/usr/bin/env python3 - -# Copyright (C) 2023 VyOS maintainers and contributors +# Copyright 2023-2024 VyOS maintainers and contributors <maintainers@vyos.io> # -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. +# 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 program is distributed in the hope that it will be useful, +# 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 General Public License for more details. +# 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 General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. # T5791: # - migrate "service dns dynamic address web web-options ..." # to "service dns dynamic name <service> address web ..." (per service) # - migrate "service dns dynamic address <address> rfc2136 <service> ..." # to "service dns dynamic name <service> address <interface> protocol 'nsupdate'" # - migrate "service dns dynamic address <interface> service <service> ..." # to "service dns dynamic name <service> address <interface> ..." # - normalize the all service names to conform with name constraints -import sys import re from unicodedata import normalize from vyos.configtree import ConfigTree def normalize_name(name): """Normalize service names to conform with name constraints. This is necessary as part of migration because there were no constraints in the old name format. """ # Normalize unicode characters to ASCII (NFKD) # Replace all separators with hypens, strip leading and trailing hyphens name = normalize('NFKD', name).encode('ascii', 'ignore').decode() name = re.sub(r'(\s|_|\W)+', '-', name).strip('-') return name - -if len(sys.argv) < 2: - print("Must specify file name!") - sys.exit(1) - -file_name = sys.argv[1] - -with open(file_name, 'r') as f: - config_file = f.read() - -config = ConfigTree(config_file) - base_path = ['service', 'dns', 'dynamic'] address_path = base_path + ['address'] name_path = base_path + ['name'] -if not config.exists(address_path): - # Nothing to do - sys.exit(0) +def migrate(config: ConfigTree) -> None: + if not config.exists(address_path): + # Nothing to do + return -# config.copy does not recursively create a path, so initialize the name path as tagged node -if not config.exists(name_path): - config.set(name_path) - config.set_tag(name_path) + # config.copy does not recursively create a path, so initialize the name path as tagged node + if not config.exists(name_path): + config.set(name_path) + config.set_tag(name_path) -for address in config.list_nodes(address_path): + for address in config.list_nodes(address_path): - address_path_tag = address_path + [address] + address_path_tag = address_path + [address] + + # Move web-option as a configuration in each service instead of top level web-option + if config.exists(address_path_tag + ['web-options']) and address == 'web': + for svc_type in ['service', 'rfc2136']: + if config.exists(address_path_tag + [svc_type]): + for svc_cfg in config.list_nodes(address_path_tag + [svc_type]): + config.copy(address_path_tag + ['web-options'], + address_path_tag + [svc_type, svc_cfg, 'web-options']) + config.delete(address_path_tag + ['web-options']) - # Move web-option as a configuration in each service instead of top level web-option - if config.exists(address_path_tag + ['web-options']) and address == 'web': for svc_type in ['service', 'rfc2136']: if config.exists(address_path_tag + [svc_type]): + # Set protocol to 'nsupdate' for RFC2136 configuration + if svc_type == 'rfc2136': + for rfc_cfg in config.list_nodes(address_path_tag + ['rfc2136']): + config.set(address_path_tag + ['rfc2136', rfc_cfg, 'protocol'], 'nsupdate') + + # Add address as config value in each service before moving the service path + # And then copy the services from 'address <interface> service <service>' + # to 'name (service|rfc2136)-<service>-<address>' + # Note: The new service is named (service|rfc2136)-<service>-<address> + # to avoid name conflict with old entries for svc_cfg in config.list_nodes(address_path_tag + [svc_type]): - config.copy(address_path_tag + ['web-options'], - address_path_tag + [svc_type, svc_cfg, 'web-options']) - config.delete(address_path_tag + ['web-options']) - - for svc_type in ['service', 'rfc2136']: - if config.exists(address_path_tag + [svc_type]): - # Set protocol to 'nsupdate' for RFC2136 configuration - if svc_type == 'rfc2136': - for rfc_cfg in config.list_nodes(address_path_tag + ['rfc2136']): - config.set(address_path_tag + ['rfc2136', rfc_cfg, 'protocol'], 'nsupdate') - - # Add address as config value in each service before moving the service path - # And then copy the services from 'address <interface> service <service>' - # to 'name (service|rfc2136)-<service>-<address>' - # Note: The new service is named (service|rfc2136)-<service>-<address> - # to avoid name conflict with old entries - for svc_cfg in config.list_nodes(address_path_tag + [svc_type]): - config.set(address_path_tag + [svc_type, svc_cfg, 'address'], address) - config.copy(address_path_tag + [svc_type, svc_cfg], - name_path + ['-'.join([svc_type, svc_cfg, address])]) - -# Finally cleanup the old address path -config.delete(address_path) - -# Normalize the all service names to conform with name constraints -index = 1 -for name in config.list_nodes(name_path): - new_name = normalize_name(name) - if new_name != name: - # Append index if there is still a name conflicts after normalization - # For example, "foo-?(" and "foo-!)" both normalize to "foo-" - if config.exists(name_path + [new_name]): - new_name = f'{new_name}-{index}' - index += 1 - config.rename(name_path + [name], new_name) - -try: - with open(file_name, 'w') as f: - f.write(config.to_string()) -except OSError as e: - print("Failed to save the modified config: {}".format(e)) - sys.exit(1) + config.set(address_path_tag + [svc_type, svc_cfg, 'address'], address) + config.copy(address_path_tag + [svc_type, svc_cfg], + name_path + ['-'.join([svc_type, svc_cfg, address])]) + + # Finally cleanup the old address path + config.delete(address_path) + + # Normalize the all service names to conform with name constraints + index = 1 + for name in config.list_nodes(name_path): + new_name = normalize_name(name) + if new_name != name: + # Append index if there is still a name conflicts after normalization + # For example, "foo-?(" and "foo-!)" both normalize to "foo-" + if config.exists(name_path + [new_name]): + new_name = f'{new_name}-{index}' + index += 1 + config.rename(name_path + [name], new_name) diff --git a/src/migration-scripts/dns-dynamic/3-to-4 b/src/migration-scripts/dns-dynamic/3-to-4 old mode 100755 new mode 100644 index b888a3b6b..c8e1ffeee --- a/src/migration-scripts/dns-dynamic/3-to-4 +++ b/src/migration-scripts/dns-dynamic/3-to-4 @@ -1,76 +1,57 @@ -#!/usr/bin/env python3 - -# Copyright (C) 2024 VyOS maintainers and contributors +# Copyright 2024 VyOS maintainers and contributors <maintainers@vyos.io> # -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. +# 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 program is distributed in the hope that it will be useful, +# 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 General Public License for more details. +# 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 General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. # T5966: # - migrate "service dns dynamic name <service> address <interface>" # to "service dns dynamic name <service> address interface <interface>" # when <interface> != 'web' # - migrate "service dns dynamic name <service> web-options ..." # to "service dns dynamic name <service> address web ..." # when <interface> == 'web' -import sys from vyos.configtree import ConfigTree -if len(sys.argv) < 2: - print("Must specify file name!") - sys.exit(1) - -file_name = sys.argv[1] - -with open(file_name, 'r') as f: - config_file = f.read() - -config = ConfigTree(config_file) - base_path = ['service', 'dns', 'dynamic', 'name'] -if not config.exists(base_path): - # Nothing to do - sys.exit(0) - -for service in config.list_nodes(base_path): +def migrate(config: ConfigTree) -> None: + if not config.exists(base_path): + # Nothing to do + return - service_path = base_path + [service] + for service in config.list_nodes(base_path): - if config.exists(service_path + ['address']): - address = config.return_value(service_path + ['address']) - # 'address' is not a leaf node anymore, delete it first - config.delete(service_path + ['address']) + service_path = base_path + [service] - # When address is an interface (not 'web'), move it to 'address interface' - if address != 'web': - config.set(service_path + ['address', 'interface'], address) + if config.exists(service_path + ['address']): + address = config.return_value(service_path + ['address']) + # 'address' is not a leaf node anymore, delete it first + config.delete(service_path + ['address']) - else: # address == 'web' - # Relocate optional 'web-options' directly under 'address web' - if config.exists(service_path + ['web-options']): - # config.copy does not recursively create a path, so initialize it - config.set(service_path + ['address']) - config.copy(service_path + ['web-options'], - service_path + ['address', 'web']) - config.delete(service_path + ['web-options']) + # When address is an interface (not 'web'), move it to 'address interface' + if address != 'web': + config.set(service_path + ['address', 'interface'], address) - # ensure that valueless 'address web' still exists even if there are no 'web-options' - if not config.exists(service_path + ['address', 'web']): - config.set(service_path + ['address', 'web']) + else: # address == 'web' + # Relocate optional 'web-options' directly under 'address web' + if config.exists(service_path + ['web-options']): + # config.copy does not recursively create a path, so initialize it + config.set(service_path + ['address']) + config.copy(service_path + ['web-options'], + service_path + ['address', 'web']) + config.delete(service_path + ['web-options']) -try: - with open(file_name, 'w') as f: - f.write(config.to_string()) -except OSError as e: - print("Failed to save the modified config: {}".format(e)) - sys.exit(1) + # ensure that valueless 'address web' still exists even if there are no 'web-options' + if not config.exists(service_path + ['address', 'web']): + config.set(service_path + ['address', 'web']) diff --git a/src/migration-scripts/dns-forwarding/0-to-1 b/src/migration-scripts/dns-forwarding/0-to-1 old mode 100755 new mode 100644 index 7f4343652..264ffb40d --- a/src/migration-scripts/dns-forwarding/0-to-1 +++ b/src/migration-scripts/dns-forwarding/0-to-1 @@ -1,50 +1,31 @@ -#!/usr/bin/env python3 +# Copyright 2019-2024 VyOS maintainers and contributors <maintainers@vyos.io> # -# Copyright (C) 2019 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 program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, +# 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 General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. +# 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 <http://www.gnu.org/licenses/>. # This migration script will check if there is a allow-from directive configured # for the dns forwarding service - if not, the node will be created with the old # default values of 0.0.0.0/0 and ::/0 -import sys from vyos.configtree import ConfigTree -if len(sys.argv) < 2: - print("Must specify file name!") - sys.exit(1) - -file_name = sys.argv[1] - -with open(file_name, 'r') as f: - config_file = f.read() +base = ['service', 'dns', 'forwarding'] -config = ConfigTree(config_file) +def migrate(config: ConfigTree)-> None: + if not config.exists(base): + # Nothing to do + return -base = ['service', 'dns', 'forwarding'] -if not config.exists(base): - # Nothing to do - sys.exit(0) -else: if not config.exists(base + ['allow-from']): config.set(base + ['allow-from'], value='0.0.0.0/0', replace=False) config.set(base + ['allow-from'], value='::/0', replace=False) - - try: - with open(file_name, 'w') as f: - f.write(config.to_string()) - except OSError as e: - print("Failed to save the modified config: {}".format(e)) - sys.exit(1) diff --git a/src/migration-scripts/dns-forwarding/1-to-2 b/src/migration-scripts/dns-forwarding/1-to-2 old mode 100755 new mode 100644 index 7df2d47e2..15ed1e136 --- a/src/migration-scripts/dns-forwarding/1-to-2 +++ b/src/migration-scripts/dns-forwarding/1-to-2 @@ -1,86 +1,67 @@ -#!/usr/bin/env python3 +# Copyright 2019-2024 VyOS maintainers and contributors <maintainers@vyos.io> # -# Copyright (C) 2019 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 program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, +# 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 General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. +# 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 <http://www.gnu.org/licenses/>. # This migration script will remove the deprecated 'listen-on' statement # from the dns forwarding service and will add the corresponding # listen-address nodes instead. This is required as PowerDNS can only listen # on interface addresses and not on interface names. from ipaddress import ip_interface -from sys import argv, exit from vyos.ifconfig import Interface from vyos.configtree import ConfigTree -if len(argv) < 2: - print("Must specify file name!") - exit(1) - -file_name = argv[1] - -with open(file_name, 'r') as f: - config_file = f.read() - -config = ConfigTree(config_file) - base = ['service', 'dns', 'forwarding'] -if not config.exists(base + ['listen-on']): - # Nothing to do - exit(0) -listen_intf = config.return_values(base + ['listen-on']) -# Delete node with abandoned command -config.delete(base + ['listen-on']) +def migrate(config: ConfigTree) -> None: + if not config.exists(base + ['listen-on']): + # Nothing to do + return -# retrieve interface addresses for every configured listen-on interface -listen_addr = [] -for intf in listen_intf: - # we need to evaluate the interface section before manipulating the 'intf' variable - section = Interface.section(intf) - if not section: - raise ValueError(f'Invalid interface name {intf}') + listen_intf = config.return_values(base + ['listen-on']) + # Delete node with abandoned command + config.delete(base + ['listen-on']) - # we need to treat vif and vif-s interfaces differently, - # both "real interfaces" use dots for vlan identifiers - those - # need to be exchanged with vif and vif-s identifiers - if intf.count('.') == 1: - # this is a regular VLAN interface - intf = intf.split('.')[0] + ' vif ' + intf.split('.')[1] - elif intf.count('.') == 2: - # this is a QinQ VLAN interface - intf = intf.split('.')[0] + ' vif-s ' + intf.split('.')[1] + ' vif-c ' + intf.split('.')[2] + # retrieve interface addresses for every configured listen-on interface + listen_addr = [] + for intf in listen_intf: + # we need to evaluate the interface section before manipulating the 'intf' variable + section = Interface.section(intf) + if not section: + raise ValueError(f'Invalid interface name {intf}') - # retrieve corresponding interface addresses in CIDR format - # those need to be converted in pure IP addresses without network information - path = ['interfaces', section, intf, 'address'] - try: - for addr in config.return_values(path): - listen_addr.append( ip_interface(addr).ip ) - except: - # Some interface types do not use "address" option (e.g. OpenVPN) - # and may not even have a fixed address - print("Could not retrieve the address of the interface {} from the config".format(intf)) - print("You will need to update your DNS forwarding configuration manually") + # we need to treat vif and vif-s interfaces differently, + # both "real interfaces" use dots for vlan identifiers - those + # need to be exchanged with vif and vif-s identifiers + if intf.count('.') == 1: + # this is a regular VLAN interface + intf = intf.split('.')[0] + ' vif ' + intf.split('.')[1] + elif intf.count('.') == 2: + # this is a QinQ VLAN interface + intf = intf.split('.')[0] + ' vif-s ' + intf.split('.')[1] + ' vif-c ' + intf.split('.')[2] -for addr in listen_addr: - config.set(base + ['listen-address'], value=addr, replace=False) + # retrieve corresponding interface addresses in CIDR format + # those need to be converted in pure IP addresses without network information + path = ['interfaces', section, intf, 'address'] + try: + for addr in config.return_values(path): + listen_addr.append( ip_interface(addr).ip ) + except: + # Some interface types do not use "address" option (e.g. OpenVPN) + # and may not even have a fixed address + print("Could not retrieve the address of the interface {} from the config".format(intf)) + print("You will need to update your DNS forwarding configuration manually") -try: - with open(file_name, 'w') as f: - f.write(config.to_string()) -except OSError as e: - print(f'Failed to save the modified config: {e}') - exit(1) + for addr in listen_addr: + config.set(base + ['listen-address'], value=addr, replace=False) diff --git a/src/migration-scripts/dns-forwarding/2-to-3 b/src/migration-scripts/dns-forwarding/2-to-3 old mode 100755 new mode 100644 index d7ff9e260..729c1f00a --- a/src/migration-scripts/dns-forwarding/2-to-3 +++ b/src/migration-scripts/dns-forwarding/2-to-3 @@ -1,51 +1,32 @@ -#!/usr/bin/env python3 +# Copyright 2020-2024 VyOS maintainers and contributors <maintainers@vyos.io> # -# Copyright (C) 2020 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 program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, +# 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 General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. +# 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 <http://www.gnu.org/licenses/>. # Sets the new options "addnta" and "recursion-desired" for all # 'dns forwarding domain' as this is usually desired -import sys from vyos.configtree import ConfigTree -if len(sys.argv) < 2: - print("Must specify file name!") - sys.exit(1) - -file_name = sys.argv[1] - -with open(file_name, 'r') as f: - config_file = f.read() - -config = ConfigTree(config_file) - base = ['service', 'dns', 'forwarding'] -if not config.exists(base): - # Nothing to do - sys.exit(0) -if config.exists(base + ['domain']): - for domain in config.list_nodes(base + ['domain']): - domain_base = base + ['domain', domain] - config.set(domain_base + ['addnta']) - config.set(domain_base + ['recursion-desired']) +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + # Nothing to do + return - try: - with open(file_name, 'w') as f: - f.write(config.to_string()) - except OSError as e: - print("Failed to save the modified config: {}".format(e)) - sys.exit(1) + if config.exists(base + ['domain']): + for domain in config.list_nodes(base + ['domain']): + domain_base = base + ['domain', domain] + config.set(domain_base + ['addnta']) + config.set(domain_base + ['recursion-desired']) diff --git a/src/migration-scripts/dns-forwarding/3-to-4 b/src/migration-scripts/dns-forwarding/3-to-4 old mode 100755 new mode 100644 index 3d5316ed4..b02c0b7ca --- a/src/migration-scripts/dns-forwarding/3-to-4 +++ b/src/migration-scripts/dns-forwarding/3-to-4 @@ -1,49 +1,31 @@ -#!/usr/bin/env python3 - -# Copyright (C) 2023 VyOS maintainers and contributors +# Copyright 2023-2024 VyOS maintainers and contributors <maintainers@vyos.io> # -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. +# 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 program is distributed in the hope that it will be useful, +# 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 General Public License for more details. +# 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 General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. # T5115: migrate "service dns forwarding domain example.com server" to # "service dns forwarding domain example.com name-server" -import sys from vyos.configtree import ConfigTree -if len(sys.argv) < 2: - print("Must specify file name!") - sys.exit(1) - -file_name = sys.argv[1] - -with open(file_name, 'r') as f: - config_file = f.read() - -config = ConfigTree(config_file) - base = ['service', 'dns', 'forwarding', 'domain'] -if not config.exists(base): - # Nothing to do - sys.exit(0) -for domain in config.list_nodes(base): - if config.exists(base + [domain, 'server']): - config.copy(base + [domain, 'server'], base + [domain, 'name-server']) - config.delete(base + [domain, 'server']) +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + # Nothing to do + return -try: - with open(file_name, 'w') as f: - f.write(config.to_string()) -except OSError as e: - print("Failed to save the modified config: {}".format(e)) - sys.exit(1) + for domain in config.list_nodes(base): + if config.exists(base + [domain, 'server']): + config.copy(base + [domain, 'server'], base + [domain, 'name-server']) + config.delete(base + [domain, 'server']) diff --git a/src/migration-scripts/firewall/10-to-11 b/src/migration-scripts/firewall/10-to-11 old mode 100755 new mode 100644 index 854d5a558..70a170940 --- a/src/migration-scripts/firewall/10-to-11 +++ b/src/migration-scripts/firewall/10-to-11 @@ -1,207 +1,187 @@ -#!/usr/bin/env python3 +# Copyright 2023-2024 VyOS maintainers and contributors <maintainers@vyos.io> # -# Copyright (C) 2023-2024 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 program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, +# 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 General Public License for more details. +# 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 General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. # T5160: Firewall re-writing # cli changes from: # set firewall name <name> ... # set firewall ipv6-name <name> ... # To # set firewall ipv4 name <name> # set firewall ipv6 name <name> ## Also from 'firewall interface' removed. ## in and out: # set firewall interface <iface> [in|out] [name | ipv6-name] <name> # To # set firewall [ipv4 | ipv6] forward filter rule <5,10,15,...> [inbound-interface | outboubd-interface] interface-name <iface> # set firewall [ipv4 | ipv6] forward filter rule <5,10,15,...> action jump # set firewall [ipv4 | ipv6] forward filter rule <5,10,15,...> jump-target <name> ## local: # set firewall interface <iface> local [name | ipv6-name] <name> # To # set firewall [ipv4 | ipv6] input filter rule <5,10,15,...> inbound-interface interface-name <iface> # set firewall [ipv4 | ipv6] input filter rule <5,10,15,...> action jump # set firewall [ipv4 | ipv6] input filter rule <5,10,15,...> jump-target <name> -from sys import argv -from sys import exit - from vyos.configtree import ConfigTree -if len(argv) < 2: - print("Must specify file name!") - exit(1) - -file_name = argv[1] - -with open(file_name, 'r') as f: - config_file = f.read() - base = ['firewall'] -config = ConfigTree(config_file) - -if not config.exists(base): - # Nothing to do - exit(0) - -### Migration of state policies -if config.exists(base + ['state-policy']): - for state in config.list_nodes(base + ['state-policy']): - action = config.return_value(base + ['state-policy', state, 'action']) - config.set(base + ['global-options', 'state-policy', state, 'action'], value=action) - if config.exists(base + ['state-policy', state, 'log']): - config.set(base + ['global-options', 'state-policy', state, 'log'], value='enable') - config.delete(base + ['state-policy']) - -## migration of global options: -for option in ['all-ping', 'broadcast-ping', 'config-trap', 'ip-src-route', 'ipv6-receive-redirects', 'ipv6-src-route', 'log-martians', - 'receive-redirects', 'resolver-cache', 'resolver-internal', 'send-redirects', 'source-validation', 'syn-cookies', 'twa-hazards-protection']: - if config.exists(base + [option]): - if option != 'config-trap': - val = config.return_value(base + [option]) - config.set(base + ['global-options', option], value=val) - config.delete(base + [option]) - -### Migration of firewall name and ipv6-name -### Also migrate legacy 'accept' behaviour -if config.exists(base + ['name']): - config.set(['firewall', 'ipv4', 'name']) - config.set_tag(['firewall', 'ipv4', 'name']) - - for ipv4name in config.list_nodes(base + ['name']): - config.copy(base + ['name', ipv4name], base + ['ipv4', 'name', ipv4name]) - - if config.exists(base + ['ipv4', 'name', ipv4name, 'default-action']): - action = config.return_value(base + ['ipv4', 'name', ipv4name, 'default-action']) - - if action == 'accept': - config.set(base + ['ipv4', 'name', ipv4name, 'default-action'], value='return') - - if config.exists(base + ['ipv4', 'name', ipv4name, 'rule']): - for rule_id in config.list_nodes(base + ['ipv4', 'name', ipv4name, 'rule']): - action = config.return_value(base + ['ipv4', 'name', ipv4name, 'rule', rule_id, 'action']) + +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + # Nothing to do + return + + ### Migration of state policies + if config.exists(base + ['state-policy']): + for state in config.list_nodes(base + ['state-policy']): + action = config.return_value(base + ['state-policy', state, 'action']) + config.set(base + ['global-options', 'state-policy', state, 'action'], value=action) + if config.exists(base + ['state-policy', state, 'log']): + config.set(base + ['global-options', 'state-policy', state, 'log'], value='enable') + config.delete(base + ['state-policy']) + + ## migration of global options: + for option in ['all-ping', 'broadcast-ping', 'config-trap', 'ip-src-route', 'ipv6-receive-redirects', 'ipv6-src-route', 'log-martians', + 'receive-redirects', 'resolver-cache', 'resolver-internal', 'send-redirects', 'source-validation', 'syn-cookies', 'twa-hazards-protection']: + if config.exists(base + [option]): + if option != 'config-trap': + val = config.return_value(base + [option]) + config.set(base + ['global-options', option], value=val) + config.delete(base + [option]) + + ### Migration of firewall name and ipv6-name + ### Also migrate legacy 'accept' behaviour + if config.exists(base + ['name']): + config.set(['firewall', 'ipv4', 'name']) + config.set_tag(['firewall', 'ipv4', 'name']) + + for ipv4name in config.list_nodes(base + ['name']): + config.copy(base + ['name', ipv4name], base + ['ipv4', 'name', ipv4name]) + + if config.exists(base + ['ipv4', 'name', ipv4name, 'default-action']): + action = config.return_value(base + ['ipv4', 'name', ipv4name, 'default-action']) if action == 'accept': - config.set(base + ['ipv4', 'name', ipv4name, 'rule', rule_id, 'action'], value='return') + config.set(base + ['ipv4', 'name', ipv4name, 'default-action'], value='return') - config.delete(base + ['name']) + if config.exists(base + ['ipv4', 'name', ipv4name, 'rule']): + for rule_id in config.list_nodes(base + ['ipv4', 'name', ipv4name, 'rule']): + action = config.return_value(base + ['ipv4', 'name', ipv4name, 'rule', rule_id, 'action']) -if config.exists(base + ['ipv6-name']): - config.set(['firewall', 'ipv6', 'name']) - config.set_tag(['firewall', 'ipv6', 'name']) + if action == 'accept': + config.set(base + ['ipv4', 'name', ipv4name, 'rule', rule_id, 'action'], value='return') - for ipv6name in config.list_nodes(base + ['ipv6-name']): - config.copy(base + ['ipv6-name', ipv6name], base + ['ipv6', 'name', ipv6name]) + config.delete(base + ['name']) - if config.exists(base + ['ipv6', 'name', ipv6name, 'default-action']): - action = config.return_value(base + ['ipv6', 'name', ipv6name, 'default-action']) + if config.exists(base + ['ipv6-name']): + config.set(['firewall', 'ipv6', 'name']) + config.set_tag(['firewall', 'ipv6', 'name']) - if action == 'accept': - config.set(base + ['ipv6', 'name', ipv6name, 'default-action'], value='return') + for ipv6name in config.list_nodes(base + ['ipv6-name']): + config.copy(base + ['ipv6-name', ipv6name], base + ['ipv6', 'name', ipv6name]) - if config.exists(base + ['ipv6', 'name', ipv6name, 'rule']): - for rule_id in config.list_nodes(base + ['ipv6', 'name', ipv6name, 'rule']): - action = config.return_value(base + ['ipv6', 'name', ipv6name, 'rule', rule_id, 'action']) + if config.exists(base + ['ipv6', 'name', ipv6name, 'default-action']): + action = config.return_value(base + ['ipv6', 'name', ipv6name, 'default-action']) if action == 'accept': - config.set(base + ['ipv6', 'name', ipv6name, 'rule', rule_id, 'action'], value='return') - - config.delete(base + ['ipv6-name']) - -### Migration of firewall interface -if config.exists(base + ['interface']): - fwd_ipv4_rule = 5 - inp_ipv4_rule = 5 - fwd_ipv6_rule = 5 - inp_ipv6_rule = 5 - for direction in ['in', 'out', 'local']: - for iface in config.list_nodes(base + ['interface']): - if config.exists(base + ['interface', iface, direction]): - if config.exists(base + ['interface', iface, direction, 'name']): - target = config.return_value(base + ['interface', iface, direction, 'name']) - if direction == 'in': - # Add default-action== accept for compatibility reasons: - config.set(base + ['ipv4', 'forward', 'filter', 'default-action'], value='accept') - new_base = base + ['ipv4', 'forward', 'filter', 'rule'] - config.set(new_base) - config.set_tag(new_base) - config.set(new_base + [fwd_ipv4_rule, 'inbound-interface', 'interface-name'], value=iface) - config.set(new_base + [fwd_ipv4_rule, 'action'], value='jump') - config.set(new_base + [fwd_ipv4_rule, 'jump-target'], value=target) - fwd_ipv4_rule = fwd_ipv4_rule + 5 - elif direction == 'out': - # Add default-action== accept for compatibility reasons: - config.set(base + ['ipv4', 'forward', 'filter', 'default-action'], value='accept') - new_base = base + ['ipv4', 'forward', 'filter', 'rule'] - config.set(new_base) - config.set_tag(new_base) - config.set(new_base + [fwd_ipv4_rule, 'outbound-interface', 'interface-name'], value=iface) - config.set(new_base + [fwd_ipv4_rule, 'action'], value='jump') - config.set(new_base + [fwd_ipv4_rule, 'jump-target'], value=target) - fwd_ipv4_rule = fwd_ipv4_rule + 5 - else: - # Add default-action== accept for compatibility reasons: - config.set(base + ['ipv4', 'input', 'filter', 'default-action'], value='accept') - new_base = base + ['ipv4', 'input', 'filter', 'rule'] - config.set(new_base) - config.set_tag(new_base) - config.set(new_base + [inp_ipv4_rule, 'inbound-interface', 'interface-name'], value=iface) - config.set(new_base + [inp_ipv4_rule, 'action'], value='jump') - config.set(new_base + [inp_ipv4_rule, 'jump-target'], value=target) - inp_ipv4_rule = inp_ipv4_rule + 5 - - if config.exists(base + ['interface', iface, direction, 'ipv6-name']): - target = config.return_value(base + ['interface', iface, direction, 'ipv6-name']) - if direction == 'in': - # Add default-action== accept for compatibility reasons: - config.set(base + ['ipv6', 'forward', 'filter', 'default-action'], value='accept') - new_base = base + ['ipv6', 'forward', 'filter', 'rule'] - config.set(new_base) - config.set_tag(new_base) - config.set(new_base + [fwd_ipv6_rule, 'inbound-interface', 'interface-name'], value=iface) - config.set(new_base + [fwd_ipv6_rule, 'action'], value='jump') - config.set(new_base + [fwd_ipv6_rule, 'jump-target'], value=target) - fwd_ipv6_rule = fwd_ipv6_rule + 5 - elif direction == 'out': - # Add default-action== accept for compatibility reasons: - config.set(base + ['ipv6', 'forward', 'filter', 'default-action'], value='accept') - new_base = base + ['ipv6', 'forward', 'filter', 'rule'] - config.set(new_base) - config.set_tag(new_base) - config.set(new_base + [fwd_ipv6_rule, 'outbound-interface', 'interface-name'], value=iface) - config.set(new_base + [fwd_ipv6_rule, 'action'], value='jump') - config.set(new_base + [fwd_ipv6_rule, 'jump-target'], value=target) - fwd_ipv6_rule = fwd_ipv6_rule + 5 - else: - new_base = base + ['ipv6', 'input', 'filter', 'rule'] - # Add default-action== accept for compatibility reasons: - config.set(base + ['ipv6', 'input', 'filter', 'default-action'], value='accept') - config.set(new_base) - config.set_tag(new_base) - config.set(new_base + [inp_ipv6_rule, 'inbound-interface', 'interface-name'], value=iface) - config.set(new_base + [inp_ipv6_rule, 'action'], value='jump') - config.set(new_base + [inp_ipv6_rule, 'jump-target'], value=target) - inp_ipv6_rule = inp_ipv6_rule + 5 - - config.delete(base + ['interface']) - -try: - with open(file_name, 'w') as f: - f.write(config.to_string()) -except OSError as e: - print("Failed to save the modified config: {}".format(e)) - exit(1) + config.set(base + ['ipv6', 'name', ipv6name, 'default-action'], value='return') + + if config.exists(base + ['ipv6', 'name', ipv6name, 'rule']): + for rule_id in config.list_nodes(base + ['ipv6', 'name', ipv6name, 'rule']): + action = config.return_value(base + ['ipv6', 'name', ipv6name, 'rule', rule_id, 'action']) + + if action == 'accept': + config.set(base + ['ipv6', 'name', ipv6name, 'rule', rule_id, 'action'], value='return') + + config.delete(base + ['ipv6-name']) + + ### Migration of firewall interface + if config.exists(base + ['interface']): + fwd_ipv4_rule = 5 + inp_ipv4_rule = 5 + fwd_ipv6_rule = 5 + inp_ipv6_rule = 5 + for direction in ['in', 'out', 'local']: + for iface in config.list_nodes(base + ['interface']): + if config.exists(base + ['interface', iface, direction]): + if config.exists(base + ['interface', iface, direction, 'name']): + target = config.return_value(base + ['interface', iface, direction, 'name']) + if direction == 'in': + # Add default-action== accept for compatibility reasons: + config.set(base + ['ipv4', 'forward', 'filter', 'default-action'], value='accept') + new_base = base + ['ipv4', 'forward', 'filter', 'rule'] + config.set(new_base) + config.set_tag(new_base) + config.set(new_base + [fwd_ipv4_rule, 'inbound-interface', 'interface-name'], value=iface) + config.set(new_base + [fwd_ipv4_rule, 'action'], value='jump') + config.set(new_base + [fwd_ipv4_rule, 'jump-target'], value=target) + fwd_ipv4_rule = fwd_ipv4_rule + 5 + elif direction == 'out': + # Add default-action== accept for compatibility reasons: + config.set(base + ['ipv4', 'forward', 'filter', 'default-action'], value='accept') + new_base = base + ['ipv4', 'forward', 'filter', 'rule'] + config.set(new_base) + config.set_tag(new_base) + config.set(new_base + [fwd_ipv4_rule, 'outbound-interface', 'interface-name'], value=iface) + config.set(new_base + [fwd_ipv4_rule, 'action'], value='jump') + config.set(new_base + [fwd_ipv4_rule, 'jump-target'], value=target) + fwd_ipv4_rule = fwd_ipv4_rule + 5 + else: + # Add default-action== accept for compatibility reasons: + config.set(base + ['ipv4', 'input', 'filter', 'default-action'], value='accept') + new_base = base + ['ipv4', 'input', 'filter', 'rule'] + config.set(new_base) + config.set_tag(new_base) + config.set(new_base + [inp_ipv4_rule, 'inbound-interface', 'interface-name'], value=iface) + config.set(new_base + [inp_ipv4_rule, 'action'], value='jump') + config.set(new_base + [inp_ipv4_rule, 'jump-target'], value=target) + inp_ipv4_rule = inp_ipv4_rule + 5 + + if config.exists(base + ['interface', iface, direction, 'ipv6-name']): + target = config.return_value(base + ['interface', iface, direction, 'ipv6-name']) + if direction == 'in': + # Add default-action== accept for compatibility reasons: + config.set(base + ['ipv6', 'forward', 'filter', 'default-action'], value='accept') + new_base = base + ['ipv6', 'forward', 'filter', 'rule'] + config.set(new_base) + config.set_tag(new_base) + config.set(new_base + [fwd_ipv6_rule, 'inbound-interface', 'interface-name'], value=iface) + config.set(new_base + [fwd_ipv6_rule, 'action'], value='jump') + config.set(new_base + [fwd_ipv6_rule, 'jump-target'], value=target) + fwd_ipv6_rule = fwd_ipv6_rule + 5 + elif direction == 'out': + # Add default-action== accept for compatibility reasons: + config.set(base + ['ipv6', 'forward', 'filter', 'default-action'], value='accept') + new_base = base + ['ipv6', 'forward', 'filter', 'rule'] + config.set(new_base) + config.set_tag(new_base) + config.set(new_base + [fwd_ipv6_rule, 'outbound-interface', 'interface-name'], value=iface) + config.set(new_base + [fwd_ipv6_rule, 'action'], value='jump') + config.set(new_base + [fwd_ipv6_rule, 'jump-target'], value=target) + fwd_ipv6_rule = fwd_ipv6_rule + 5 + else: + new_base = base + ['ipv6', 'input', 'filter', 'rule'] + # Add default-action== accept for compatibility reasons: + config.set(base + ['ipv6', 'input', 'filter', 'default-action'], value='accept') + config.set(new_base) + config.set_tag(new_base) + config.set(new_base + [inp_ipv6_rule, 'inbound-interface', 'interface-name'], value=iface) + config.set(new_base + [inp_ipv6_rule, 'action'], value='jump') + config.set(new_base + [inp_ipv6_rule, 'jump-target'], value=target) + inp_ipv6_rule = inp_ipv6_rule + 5 + + config.delete(base + ['interface']) diff --git a/src/migration-scripts/firewall/11-to-12 b/src/migration-scripts/firewall/11-to-12 old mode 100755 new mode 100644 index f9122e74c..80a74cca9 --- a/src/migration-scripts/firewall/11-to-12 +++ b/src/migration-scripts/firewall/11-to-12 @@ -1,71 +1,51 @@ -#!/usr/bin/env python3 +# Copyright 2023-2024 VyOS maintainers and contributors <maintainers@vyos.io> # -# Copyright (C) 2023-2024 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 program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, +# 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 General Public License for more details. +# 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 General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. # T5681: Firewall re-writing. Simplify cli when mathcing interface # From # set firewall ... rule <rule> [inbound-interface | outboubd-interface] interface-name <iface> # set firewall ... rule <rule> [inbound-interface | outboubd-interface] interface-group <iface_group> # To # set firewall ... rule <rule> [inbound-interface | outboubd-interface] name <iface> # set firewall ... rule <rule> [inbound-interface | outboubd-interface] group <iface_group> -from sys import argv -from sys import exit - from vyos.configtree import ConfigTree -if len(argv) < 2: - print("Must specify file name!") - exit(1) - -file_name = argv[1] - -with open(file_name, 'r') as f: - config_file = f.read() - base = ['firewall'] -config = ConfigTree(config_file) - -if not config.exists(base): - # Nothing to do - exit(0) - -## Migration from base chains -#if config.exists(base + ['interface', iface, direction]): -for family in ['ipv4', 'ipv6']: - if config.exists(base + [family]): - for hook in ['forward', 'input', 'output', 'name']: - if config.exists(base + [family, hook]): - for priority in config.list_nodes(base + [family, hook]): - if config.exists(base + [family, hook, priority, 'rule']): - for rule in config.list_nodes(base + [family, hook, priority, 'rule']): - for direction in ['inbound-interface', 'outbound-interface']: - if config.exists(base + [family, hook, priority, 'rule', rule, direction]): - if config.exists(base + [family, hook, priority, 'rule', rule, direction, 'interface-name']): - iface = config.return_value(base + [family, hook, priority, 'rule', rule, direction, 'interface-name']) - config.set(base + [family, hook, priority, 'rule', rule, direction, 'name'], value=iface) - config.delete(base + [family, hook, priority, 'rule', rule, direction, 'interface-name']) - elif config.exists(base + [family, hook, priority, 'rule', rule, direction, 'interface-group']): - group = config.return_value(base + [family, hook, priority, 'rule', rule, direction, 'interface-group']) - config.set(base + [family, hook, priority, 'rule', rule, direction, 'group'], value=group) - config.delete(base + [family, hook, priority, 'rule', rule, direction, 'interface-group']) -try: - with open(file_name, 'w') as f: - f.write(config.to_string()) -except OSError as e: - print("Failed to save the modified config: {}".format(e)) - exit(1) +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + # Nothing to do + return + + ## Migration from base chains + #if config.exists(base + ['interface', iface, direction]): + for family in ['ipv4', 'ipv6']: + if config.exists(base + [family]): + for hook in ['forward', 'input', 'output', 'name']: + if config.exists(base + [family, hook]): + for priority in config.list_nodes(base + [family, hook]): + if config.exists(base + [family, hook, priority, 'rule']): + for rule in config.list_nodes(base + [family, hook, priority, 'rule']): + for direction in ['inbound-interface', 'outbound-interface']: + if config.exists(base + [family, hook, priority, 'rule', rule, direction]): + if config.exists(base + [family, hook, priority, 'rule', rule, direction, 'interface-name']): + iface = config.return_value(base + [family, hook, priority, 'rule', rule, direction, 'interface-name']) + config.set(base + [family, hook, priority, 'rule', rule, direction, 'name'], value=iface) + config.delete(base + [family, hook, priority, 'rule', rule, direction, 'interface-name']) + elif config.exists(base + [family, hook, priority, 'rule', rule, direction, 'interface-group']): + group = config.return_value(base + [family, hook, priority, 'rule', rule, direction, 'interface-group']) + config.set(base + [family, hook, priority, 'rule', rule, direction, 'group'], value=group) + config.delete(base + [family, hook, priority, 'rule', rule, direction, 'interface-group']) diff --git a/src/migration-scripts/firewall/12-to-13 b/src/migration-scripts/firewall/12-to-13 old mode 100755 new mode 100644 index d72ba834d..d7b801cd3 --- a/src/migration-scripts/firewall/12-to-13 +++ b/src/migration-scripts/firewall/12-to-13 @@ -1,89 +1,69 @@ -#!/usr/bin/env python3 +# Copyright 2023-2024 VyOS maintainers and contributors <maintainers@vyos.io> # -# Copyright (C) 2023-2024 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 program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, +# 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 General Public License for more details. +# 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 General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. # T5729: Switch to valueless whenever is possible. # From # set firewall ... rule <rule> log enable # set firewall ... rule <rule> state <state> enable # set firewall ... rule <rule> log disable # set firewall ... rule <rule> state <state> disable # To # set firewall ... rule <rule> log # set firewall ... rule <rule> state <state> # Remove command if log=disable or <state>=disable -from sys import argv -from sys import exit - from vyos.configtree import ConfigTree -if len(argv) < 2: - print("Must specify file name!") - exit(1) - -file_name = argv[1] - -with open(file_name, 'r') as f: - config_file = f.read() - base = ['firewall'] -config = ConfigTree(config_file) - -if not config.exists(base): - # Nothing to do - exit(0) -# State Policy logs: -if config.exists(base + ['global-options', 'state-policy']): - for state in config.list_nodes(base + ['global-options', 'state-policy']): - if config.exists(base + ['global-options', 'state-policy', state, 'log']): - log_value = config.return_value(base + ['global-options', 'state-policy', state, 'log']) - config.delete(base + ['global-options', 'state-policy', state, 'log']) - if log_value == 'enable': - config.set(base + ['global-options', 'state-policy', state, 'log']) +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + # Nothing to do + return -for family in ['ipv4', 'ipv6', 'bridge']: - if config.exists(base + [family]): - for hook in ['forward', 'input', 'output', 'name']: - if config.exists(base + [family, hook]): - for priority in config.list_nodes(base + [family, hook]): - if config.exists(base + [family, hook, priority, 'rule']): - for rule in config.list_nodes(base + [family, hook, priority, 'rule']): - # Log - if config.exists(base + [family, hook, priority, 'rule', rule, 'log']): - log_value = config.return_value(base + [family, hook, priority, 'rule', rule, 'log']) - config.delete(base + [family, hook, priority, 'rule', rule, 'log']) - if log_value == 'enable': - config.set(base + [family, hook, priority, 'rule', rule, 'log']) - # State - if config.exists(base + [family, hook, priority, 'rule', rule, 'state']): - flag_enable = 'False' - for state in ['established', 'invalid', 'new', 'related']: - if config.exists(base + [family, hook, priority, 'rule', rule, 'state', state]): - state_value = config.return_value(base + [family, hook, priority, 'rule', rule, 'state', state]) - config.delete(base + [family, hook, priority, 'rule', rule, 'state', state]) - if state_value == 'enable': - config.set(base + [family, hook, priority, 'rule', rule, 'state'], value=state, replace=False) - flag_enable = 'True' - if flag_enable == 'False': - config.delete(base + [family, hook, priority, 'rule', rule, 'state']) + # State Policy logs: + if config.exists(base + ['global-options', 'state-policy']): + for state in config.list_nodes(base + ['global-options', 'state-policy']): + if config.exists(base + ['global-options', 'state-policy', state, 'log']): + log_value = config.return_value(base + ['global-options', 'state-policy', state, 'log']) + config.delete(base + ['global-options', 'state-policy', state, 'log']) + if log_value == 'enable': + config.set(base + ['global-options', 'state-policy', state, 'log']) -try: - with open(file_name, 'w') as f: - f.write(config.to_string()) -except OSError as e: - print("Failed to save the modified config: {}".format(e)) - exit(1) + for family in ['ipv4', 'ipv6', 'bridge']: + if config.exists(base + [family]): + for hook in ['forward', 'input', 'output', 'name']: + if config.exists(base + [family, hook]): + for priority in config.list_nodes(base + [family, hook]): + if config.exists(base + [family, hook, priority, 'rule']): + for rule in config.list_nodes(base + [family, hook, priority, 'rule']): + # Log + if config.exists(base + [family, hook, priority, 'rule', rule, 'log']): + log_value = config.return_value(base + [family, hook, priority, 'rule', rule, 'log']) + config.delete(base + [family, hook, priority, 'rule', rule, 'log']) + if log_value == 'enable': + config.set(base + [family, hook, priority, 'rule', rule, 'log']) + # State + if config.exists(base + [family, hook, priority, 'rule', rule, 'state']): + flag_enable = 'False' + for state in ['established', 'invalid', 'new', 'related']: + if config.exists(base + [family, hook, priority, 'rule', rule, 'state', state]): + state_value = config.return_value(base + [family, hook, priority, 'rule', rule, 'state', state]) + config.delete(base + [family, hook, priority, 'rule', rule, 'state', state]) + if state_value == 'enable': + config.set(base + [family, hook, priority, 'rule', rule, 'state'], value=state, replace=False) + flag_enable = 'True' + if flag_enable == 'False': + config.delete(base + [family, hook, priority, 'rule', rule, 'state']) diff --git a/src/migration-scripts/firewall/13-to-14 b/src/migration-scripts/firewall/13-to-14 old mode 100755 new mode 100644 index f45ff0674..723b0aea2 --- a/src/migration-scripts/firewall/13-to-14 +++ b/src/migration-scripts/firewall/13-to-14 @@ -1,59 +1,39 @@ -#!/usr/bin/env python3 +# Copyright 2023-2024 VyOS maintainers and contributors <maintainers@vyos.io> # -# Copyright (C) 2023 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 program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, +# 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 General Public License for more details. +# 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 General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. # T5834: Rename 'enable-default-log' to 'default-log' # From # set firewall ... filter enable-default-log # set firewall ... name <name> enable-default-log # To # set firewall ... filter default-log # set firewall ... name <name> default-log -from sys import argv -from sys import exit - from vyos.configtree import ConfigTree -if len(argv) < 2: - print("Must specify file name!") - exit(1) - -file_name = argv[1] - -with open(file_name, 'r') as f: - config_file = f.read() - base = ['firewall'] -config = ConfigTree(config_file) - -if not config.exists(base): - # Nothing to do - exit(0) - -for family in ['ipv4', 'ipv6', 'bridge']: - if config.exists(base + [family]): - for hook in ['forward', 'input', 'output', 'name']: - if config.exists(base + [family, hook]): - for priority in config.list_nodes(base + [family, hook]): - if config.exists(base + [family, hook, priority, 'enable-default-log']): - config.rename(base + [family, hook, priority, 'enable-default-log'], 'default-log') -try: - with open(file_name, 'w') as f: - f.write(config.to_string()) -except OSError as e: - print("Failed to save the modified config: {}".format(e)) - exit(1) +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + # Nothing to do + return + + for family in ['ipv4', 'ipv6', 'bridge']: + if config.exists(base + [family]): + for hook in ['forward', 'input', 'output', 'name']: + if config.exists(base + [family, hook]): + for priority in config.list_nodes(base + [family, hook]): + if config.exists(base + [family, hook, priority, 'enable-default-log']): + config.rename(base + [family, hook, priority, 'enable-default-log'], 'default-log') diff --git a/src/migration-scripts/firewall/14-to-15 b/src/migration-scripts/firewall/14-to-15 old mode 100755 new mode 100644 index 735839365..e4a2aaee4 --- a/src/migration-scripts/firewall/14-to-15 +++ b/src/migration-scripts/firewall/14-to-15 @@ -1,46 +1,25 @@ -#!/usr/bin/env python3 +# Copyright 2024 VyOS maintainers and contributors <maintainers@vyos.io> # -# Copyright (C) 2022-2024 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 program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, +# 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 General Public License for more details. +# 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 General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. # T5535: Migrate <set system ip disable-directed-broadcast> to <set firewall global-options directed-broadcas [enable|disable] -from sys import argv -from sys import exit - from vyos.configtree import ConfigTree -if len(argv) < 2: - print("Must specify file name!") - exit(1) - -file_name = argv[1] - -with open(file_name, 'r') as f: - config_file = f.read() - -config = ConfigTree(config_file) - base = ['firewall'] -if config.exists(['system', 'ip', 'disable-directed-broadcast']): - config.set(['firewall', 'global-options', 'directed-broadcast'], value='disable') - config.delete(['system', 'ip', 'disable-directed-broadcast']) - -try: - with open(file_name, 'w') as f: - f.write(config.to_string()) -except OSError as e: - print("Failed to save the modified config: {}".format(e)) - exit(1) \ No newline at end of file +def migrate(config: ConfigTree) -> None: + if config.exists(['system', 'ip', 'disable-directed-broadcast']): + config.set(['firewall', 'global-options', 'directed-broadcast'], value='disable') + config.delete(['system', 'ip', 'disable-directed-broadcast']) diff --git a/src/migration-scripts/firewall/15-to-16 b/src/migration-scripts/firewall/15-to-16 old mode 100755 new mode 100644 index 28df1256e..8e28bba6f --- a/src/migration-scripts/firewall/15-to-16 +++ b/src/migration-scripts/firewall/15-to-16 @@ -1,56 +1,37 @@ #!/usr/bin/env python3 # # Copyright (C) 2022-2024 VyOS maintainers and contributors # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License version 2 or later as # published by the Free Software Foundation. # # This program 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 General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. # T6394: Migrate conntrack timeout options to firewall global-options # from: set system conntrack timeout .. # to: set firewall global-options timeout ... -from sys import argv -from sys import exit - from vyos.configtree import ConfigTree -if len(argv) < 2: - print("Must specify file name!") - exit(1) - -file_name = argv[1] - -with open(file_name, 'r') as f: - config_file = f.read() - firewall_base = ['firewall', 'global-options'] conntrack_base = ['system', 'conntrack', 'timeout'] -config = ConfigTree(config_file) - -if not config.exists(conntrack_base): - # Nothing to do - exit(0) -for protocol in ['icmp', 'tcp', 'udp', 'other']: - if config.exists(conntrack_base + [protocol]): - if not config.exists(firewall_base + ['timeout']): - config.set(firewall_base + ['timeout']) +def migrate(config: ConfigTree) -> None: + if not config.exists(conntrack_base): + # Nothing to do + return - config.copy(conntrack_base + [protocol], firewall_base + ['timeout', protocol]) - config.delete(conntrack_base + [protocol]) + for protocol in ['icmp', 'tcp', 'udp', 'other']: + if config.exists(conntrack_base + [protocol]): + if not config.exists(firewall_base + ['timeout']): + config.set(firewall_base + ['timeout']) -try: - with open(file_name, 'w') as f: - f.write(config.to_string()) -except OSError as e: - print("Failed to save the modified config: {}".format(e)) - exit(1) + config.copy(conntrack_base + [protocol], firewall_base + ['timeout', protocol]) + config.delete(conntrack_base + [protocol]) diff --git a/src/migration-scripts/firewall/5-to-6 b/src/migration-scripts/firewall/5-to-6 old mode 100755 new mode 100644 index e1eaea7a1..d01684787 --- a/src/migration-scripts/firewall/5-to-6 +++ b/src/migration-scripts/firewall/5-to-6 @@ -1,105 +1,85 @@ -#!/usr/bin/env python3 +# Copyright 2021-2024 VyOS maintainers and contributors <maintainers@vyos.io> # -# Copyright (C) 2021 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 program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, +# 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 General Public License for more details. +# 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 General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. # T3090: migrate "firewall options interface <name> adjust-mss" to the # individual interface. -from sys import argv -from sys import exit - from vyos.configtree import ConfigTree from vyos.ifconfig import Section -if len(argv) < 2: - print("Must specify file name!") - exit(1) - -file_name = argv[1] - -with open(file_name, 'r') as f: - config_file = f.read() - base = ['firewall', 'options', 'interface'] -config = ConfigTree(config_file) - -if not config.exists(base): - # Nothing to do - exit(0) - -for interface in config.list_nodes(base): - if config.exists(base + [interface, 'disable']): - continue - if config.exists(base + [interface, 'adjust-mss']): - section = Section.section(interface) - tmp = config.return_value(base + [interface, 'adjust-mss']) - - vlan = interface.split('.') - base_interface_path = ['interfaces', section, vlan[0]] - - if len(vlan) == 1: - # Normal interface, no VLAN - config.set(base_interface_path + ['ip', 'adjust-mss'], value=tmp) - elif len(vlan) == 2: - # Regular VIF or VIF-S interface - we need to check the config - vif = vlan[1] - if config.exists(base_interface_path + ['vif', vif]): - config.set(base_interface_path + ['vif', vif, 'ip', 'adjust-mss'], value=tmp) - elif config.exists(base_interface_path + ['vif-s', vif]): - config.set(base_interface_path + ['vif-s', vif, 'ip', 'adjust-mss'], value=tmp) - elif len(vlan) == 3: - # VIF-S interface with VIF-C subinterface - vif_s = vlan[1] - vif_c = vlan[2] - config.set(base_interface_path + ['vif-s', vif_s, 'vif-c', vif_c, 'ip', 'adjust-mss'], value=tmp) - config.set_tag(base_interface_path + ['vif-s']) - config.set_tag(base_interface_path + ['vif-s', vif_s, 'vif-c']) - - if config.exists(base + [interface, 'adjust-mss6']): - section = Section.section(interface) - tmp = config.return_value(base + [interface, 'adjust-mss6']) - - vlan = interface.split('.') - base_interface_path = ['interfaces', section, vlan[0]] - - if len(vlan) == 1: - # Normal interface, no VLAN - config.set(['interfaces', section, interface, 'ipv6', 'adjust-mss'], value=tmp) - elif len(vlan) == 2: - # Regular VIF or VIF-S interface - we need to check the config - vif = vlan[1] - if config.exists(base_interface_path + ['vif', vif]): - config.set(base_interface_path + ['vif', vif, 'ipv6', 'adjust-mss'], value=tmp) - config.set_tag(base_interface_path + ['vif']) - elif config.exists(base_interface_path + ['vif-s', vif]): - config.set(base_interface_path + ['vif-s', vif, 'ipv6', 'adjust-mss'], value=tmp) +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + # Nothing to do + return + + for interface in config.list_nodes(base): + if config.exists(base + [interface, 'disable']): + continue + + if config.exists(base + [interface, 'adjust-mss']): + section = Section.section(interface) + tmp = config.return_value(base + [interface, 'adjust-mss']) + + vlan = interface.split('.') + base_interface_path = ['interfaces', section, vlan[0]] + + if len(vlan) == 1: + # Normal interface, no VLAN + config.set(base_interface_path + ['ip', 'adjust-mss'], value=tmp) + elif len(vlan) == 2: + # Regular VIF or VIF-S interface - we need to check the config + vif = vlan[1] + if config.exists(base_interface_path + ['vif', vif]): + config.set(base_interface_path + ['vif', vif, 'ip', 'adjust-mss'], value=tmp) + elif config.exists(base_interface_path + ['vif-s', vif]): + config.set(base_interface_path + ['vif-s', vif, 'ip', 'adjust-mss'], value=tmp) + elif len(vlan) == 3: + # VIF-S interface with VIF-C subinterface + vif_s = vlan[1] + vif_c = vlan[2] + config.set(base_interface_path + ['vif-s', vif_s, 'vif-c', vif_c, 'ip', 'adjust-mss'], value=tmp) config.set_tag(base_interface_path + ['vif-s']) - elif len(vlan) == 3: - # VIF-S interface with VIF-C subinterface - vif_s = vlan[1] - vif_c = vlan[2] - config.set(base_interface_path + ['vif-s', vif_s, 'vif-c', vif_c, 'ipv6', 'adjust-mss'], value=tmp) - config.set_tag(base_interface_path + ['vif-s']) - config.set_tag(base_interface_path + ['vif-s', vif_s, 'vif-c']) - -config.delete(['firewall', 'options']) + config.set_tag(base_interface_path + ['vif-s', vif_s, 'vif-c']) + + if config.exists(base + [interface, 'adjust-mss6']): + section = Section.section(interface) + tmp = config.return_value(base + [interface, 'adjust-mss6']) + + vlan = interface.split('.') + base_interface_path = ['interfaces', section, vlan[0]] + + if len(vlan) == 1: + # Normal interface, no VLAN + config.set(['interfaces', section, interface, 'ipv6', 'adjust-mss'], value=tmp) + elif len(vlan) == 2: + # Regular VIF or VIF-S interface - we need to check the config + vif = vlan[1] + if config.exists(base_interface_path + ['vif', vif]): + config.set(base_interface_path + ['vif', vif, 'ipv6', 'adjust-mss'], value=tmp) + config.set_tag(base_interface_path + ['vif']) + elif config.exists(base_interface_path + ['vif-s', vif]): + config.set(base_interface_path + ['vif-s', vif, 'ipv6', 'adjust-mss'], value=tmp) + config.set_tag(base_interface_path + ['vif-s']) + elif len(vlan) == 3: + # VIF-S interface with VIF-C subinterface + vif_s = vlan[1] + vif_c = vlan[2] + config.set(base_interface_path + ['vif-s', vif_s, 'vif-c', vif_c, 'ipv6', 'adjust-mss'], value=tmp) + config.set_tag(base_interface_path + ['vif-s']) + config.set_tag(base_interface_path + ['vif-s', vif_s, 'vif-c']) -try: - with open(file_name, 'w') as f: - f.write(config.to_string()) -except OSError as e: - print("Failed to save the modified config: {}".format(e)) - exit(1) + config.delete(['firewall', 'options']) diff --git a/src/migration-scripts/firewall/6-to-7 b/src/migration-scripts/firewall/6-to-7 old mode 100755 new mode 100644 index 938044c6d..1afbc780b --- a/src/migration-scripts/firewall/6-to-7 +++ b/src/migration-scripts/firewall/6-to-7 @@ -1,322 +1,304 @@ -#!/usr/bin/env python3 +# Copyright 2021-2024 VyOS maintainers and contributors <maintainers@vyos.io> # -# Copyright (C) 2021-2024 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 program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, +# 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 General Public License for more details. +# 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 General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. # T2199: Remove unavailable nodes due to XML/Python implementation using nftables # monthdays: nftables does not have a monthdays equivalent # utc: nftables userspace uses localtime and calculates the UTC offset automatically # icmp/v6: migrate previously available `type-name` to valid type/code # T4178: Update tcp flags to use multi value node # T6071: CLI description limit of 256 characters import re -from sys import argv -from sys import exit - from vyos.configtree import ConfigTree -if len(argv) < 2: - print("Must specify file name!") - exit(1) - -file_name = argv[1] - -with open(file_name, 'r') as f: - config_file = f.read() - max_len_description = 255 base = ['firewall'] -config = ConfigTree(config_file) - -if not config.exists(base): - # Nothing to do - exit(0) icmp_remove = ['any'] icmp_translations = { 'ping': 'echo-request', 'pong': 'echo-reply', 'ttl-exceeded': 'time-exceeded', # Network Unreachable 'network-unreachable': [3, 0], 'host-unreachable': [3, 1], 'protocol-unreachable': [3, 2], 'port-unreachable': [3, 3], 'fragmentation-needed': [3, 4], 'source-route-failed': [3, 5], 'network-unknown': [3, 6], 'host-unknown': [3, 7], 'network-prohibited': [3, 9], 'host-prohibited': [3, 10], 'TOS-network-unreachable': [3, 11], 'TOS-host-unreachable': [3, 12], 'communication-prohibited': [3, 13], 'host-precedence-violation': [3, 14], 'precedence-cutoff': [3, 15], # Redirect 'network-redirect': [5, 0], 'host-redirect': [5, 1], 'TOS-network-redirect': [5, 2], 'TOS host-redirect': [5, 3], # Time Exceeded 'ttl-zero-during-transit': [11, 0], 'ttl-zero-during-reassembly': [11, 1], 'ttl-exceeded': 'time-exceeded', # Parameter Problem 'ip-header-bad': [12, 0], 'required-option-missing': [12, 1] } icmpv6_remove = [] icmpv6_translations = { 'ping': 'echo-request', 'pong': 'echo-reply', # Destination Unreachable 'no-route': [1, 0], 'communication-prohibited': [1, 1], 'address-unreachble': [1, 3], 'port-unreachable': [1, 4], # nd 'redirect': 'nd-redirect', 'router-solicitation': 'nd-router-solicit', 'router-advertisement': 'nd-router-advert', 'neighbour-solicitation': 'nd-neighbor-solicit', 'neighbor-solicitation': 'nd-neighbor-solicit', 'neighbour-advertisement': 'nd-neighbor-advert', 'neighbor-advertisement': 'nd-neighbor-advert', # Time Exceeded 'ttl-zero-during-transit': [3, 0], 'ttl-zero-during-reassembly': [3, 1], # Parameter Problem 'bad-header': [4, 0], 'unknown-header-type': [4, 1], 'unknown-option': [4, 2] } -v4_found = False -v6_found = False v4_groups = ["address-group", "network-group", "port-group"] v6_groups = ["ipv6-address-group", "ipv6-network-group", "port-group"] -translated_dict = {} -if config.exists(base + ['group']): - for group_type in config.list_nodes(base + ['group']): - for group_name in config.list_nodes(base + ['group', group_type]): - name_description = base + ['group', group_type, group_name, 'description'] +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + # Nothing to do + return + + v4_found = False + v6_found = False + translated_dict = {} + + if config.exists(base + ['group']): + for group_type in config.list_nodes(base + ['group']): + for group_name in config.list_nodes(base + ['group', group_type]): + name_description = base + ['group', group_type, group_name, 'description'] + if config.exists(name_description): + tmp = config.return_value(name_description) + config.set(name_description, value=tmp[:max_len_description]) + if '+' in group_name: + replacement_string = "_" + if group_type in v4_groups and not v4_found: + v4_found = True + if group_type in v6_groups and not v6_found: + v6_found = True + new_group_name = group_name.replace('+', replacement_string) + while config.exists(base + ['group', group_type, new_group_name]): + replacement_string = replacement_string + "_" + new_group_name = group_name.replace('+', replacement_string) + translated_dict[group_name] = new_group_name + config.copy(base + ['group', group_type, group_name], base + ['group', group_type, new_group_name]) + config.delete(base + ['group', group_type, group_name]) + + if config.exists(base + ['name']): + for name in config.list_nodes(base + ['name']): + name_description = base + ['name', name, 'description'] if config.exists(name_description): tmp = config.return_value(name_description) config.set(name_description, value=tmp[:max_len_description]) - if '+' in group_name: - replacement_string = "_" - if group_type in v4_groups and not v4_found: - v4_found = True - if group_type in v6_groups and not v6_found: - v6_found = True - new_group_name = group_name.replace('+', replacement_string) - while config.exists(base + ['group', group_type, new_group_name]): - replacement_string = replacement_string + "_" - new_group_name = group_name.replace('+', replacement_string) - translated_dict[group_name] = new_group_name - config.copy(base + ['group', group_type, group_name], base + ['group', group_type, new_group_name]) - config.delete(base + ['group', group_type, group_name]) - -if config.exists(base + ['name']): - for name in config.list_nodes(base + ['name']): - name_description = base + ['name', name, 'description'] - if config.exists(name_description): - tmp = config.return_value(name_description) - config.set(name_description, value=tmp[:max_len_description]) - - if not config.exists(base + ['name', name, 'rule']): - continue - - for rule in config.list_nodes(base + ['name', name, 'rule']): - rule_description = base + ['name', name, 'rule', rule, 'description'] - if config.exists(rule_description): - tmp = config.return_value(rule_description) - config.set(rule_description, value=tmp[:max_len_description]) - - rule_recent = base + ['name', name, 'rule', rule, 'recent'] - rule_time = base + ['name', name, 'rule', rule, 'time'] - rule_tcp_flags = base + ['name', name, 'rule', rule, 'tcp', 'flags'] - rule_icmp = base + ['name', name, 'rule', rule, 'icmp'] - - if config.exists(rule_time + ['monthdays']): - config.delete(rule_time + ['monthdays']) - - if config.exists(rule_time + ['utc']): - config.delete(rule_time + ['utc']) - - if config.exists(rule_recent + ['time']): - tmp = int(config.return_value(rule_recent + ['time'])) - unit = 'minute' - if tmp > 600: - unit = 'hour' - elif tmp < 10: - unit = 'second' - config.set(rule_recent + ['time'], value=unit) - - if config.exists(rule_tcp_flags): - tmp = config.return_value(rule_tcp_flags) - config.delete(rule_tcp_flags) - for flag in tmp.split(","): - if flag[0] == '!': - config.set(rule_tcp_flags + ['not', flag[1:].lower()]) - else: - config.set(rule_tcp_flags + [flag.lower()]) - - if config.exists(rule_icmp + ['type-name']): - tmp = config.return_value(rule_icmp + ['type-name']) - if tmp in icmp_remove: - config.delete(rule_icmp + ['type-name']) - elif tmp in icmp_translations: - translate = icmp_translations[tmp] - if isinstance(translate, str): - config.set(rule_icmp + ['type-name'], value=translate) - elif isinstance(translate, list): + + if not config.exists(base + ['name', name, 'rule']): + continue + + for rule in config.list_nodes(base + ['name', name, 'rule']): + rule_description = base + ['name', name, 'rule', rule, 'description'] + if config.exists(rule_description): + tmp = config.return_value(rule_description) + config.set(rule_description, value=tmp[:max_len_description]) + + rule_recent = base + ['name', name, 'rule', rule, 'recent'] + rule_time = base + ['name', name, 'rule', rule, 'time'] + rule_tcp_flags = base + ['name', name, 'rule', rule, 'tcp', 'flags'] + rule_icmp = base + ['name', name, 'rule', rule, 'icmp'] + + if config.exists(rule_time + ['monthdays']): + config.delete(rule_time + ['monthdays']) + + if config.exists(rule_time + ['utc']): + config.delete(rule_time + ['utc']) + + if config.exists(rule_recent + ['time']): + tmp = int(config.return_value(rule_recent + ['time'])) + unit = 'minute' + if tmp > 600: + unit = 'hour' + elif tmp < 10: + unit = 'second' + config.set(rule_recent + ['time'], value=unit) + + if config.exists(rule_tcp_flags): + tmp = config.return_value(rule_tcp_flags) + config.delete(rule_tcp_flags) + for flag in tmp.split(","): + if flag[0] == '!': + config.set(rule_tcp_flags + ['not', flag[1:].lower()]) + else: + config.set(rule_tcp_flags + [flag.lower()]) + + if config.exists(rule_icmp + ['type-name']): + tmp = config.return_value(rule_icmp + ['type-name']) + if tmp in icmp_remove: config.delete(rule_icmp + ['type-name']) - config.set(rule_icmp + ['type'], value=translate[0]) - config.set(rule_icmp + ['code'], value=translate[1]) - - for direction in ['destination', 'source']: - if config.exists(base + ['name', name, 'rule', rule, direction]): - if config.exists(base + ['name', name, 'rule', rule, direction, 'group']) and v4_found: - for group_type in config.list_nodes(base + ['name', name, 'rule', rule, direction, 'group']): - group_name = config.return_value(base + ['name', name, 'rule', rule, direction, 'group', group_type]) - if '+' in group_name: - if group_name[0] == "!": - new_group_name = "!" + translated_dict[group_name[1:]] - else: - new_group_name = translated_dict[group_name] - config.set(base + ['name', name, 'rule', rule, direction, 'group', group_type], value=new_group_name) - - pg_base = base + ['name', name, 'rule', rule, direction, 'group', 'port-group'] - proto_base = base + ['name', name, 'rule', rule, 'protocol'] - if config.exists(pg_base) and not config.exists(proto_base): - config.set(proto_base, value='tcp_udp') - - if '+' in name: - replacement_string = "_" - new_name = name.replace('+', replacement_string) - while config.exists(base + ['name', new_name]): - replacement_string = replacement_string + "_" + elif tmp in icmp_translations: + translate = icmp_translations[tmp] + if isinstance(translate, str): + config.set(rule_icmp + ['type-name'], value=translate) + elif isinstance(translate, list): + config.delete(rule_icmp + ['type-name']) + config.set(rule_icmp + ['type'], value=translate[0]) + config.set(rule_icmp + ['code'], value=translate[1]) + + for direction in ['destination', 'source']: + if config.exists(base + ['name', name, 'rule', rule, direction]): + if config.exists(base + ['name', name, 'rule', rule, direction, 'group']) and v4_found: + for group_type in config.list_nodes(base + ['name', name, 'rule', rule, direction, 'group']): + group_name = config.return_value(base + ['name', name, 'rule', rule, direction, 'group', group_type]) + if '+' in group_name: + if group_name[0] == "!": + new_group_name = "!" + translated_dict[group_name[1:]] + else: + new_group_name = translated_dict[group_name] + config.set(base + ['name', name, 'rule', rule, direction, 'group', group_type], value=new_group_name) + + pg_base = base + ['name', name, 'rule', rule, direction, 'group', 'port-group'] + proto_base = base + ['name', name, 'rule', rule, 'protocol'] + if config.exists(pg_base) and not config.exists(proto_base): + config.set(proto_base, value='tcp_udp') + + if '+' in name: + replacement_string = "_" new_name = name.replace('+', replacement_string) - config.copy(base + ['name', name], base + ['name', new_name]) - config.delete(base + ['name', name]) - -if config.exists(base + ['ipv6-name']): - for name in config.list_nodes(base + ['ipv6-name']): - name_description = base + ['ipv6-name', name, 'description'] - if config.exists(name_description): - tmp = config.return_value(name_description) - config.set(name_description, value=tmp[:max_len_description]) - - if not config.exists(base + ['ipv6-name', name, 'rule']): - continue - - for rule in config.list_nodes(base + ['ipv6-name', name, 'rule']): - rule_description = base + ['ipv6-name', name, 'rule', rule, 'description'] - if config.exists(rule_description): - tmp = config.return_value(rule_description) - config.set(rule_description, value=tmp[:max_len_description]) - - rule_recent = base + ['ipv6-name', name, 'rule', rule, 'recent'] - rule_time = base + ['ipv6-name', name, 'rule', rule, 'time'] - rule_tcp_flags = base + ['ipv6-name', name, 'rule', rule, 'tcp', 'flags'] - rule_icmp = base + ['ipv6-name', name, 'rule', rule, 'icmpv6'] - - if config.exists(rule_time + ['monthdays']): - config.delete(rule_time + ['monthdays']) - - if config.exists(rule_time + ['utc']): - config.delete(rule_time + ['utc']) - - if config.exists(rule_recent + ['time']): - tmp = int(config.return_value(rule_recent + ['time'])) - unit = 'minute' - if tmp > 600: - unit = 'hour' - elif tmp < 10: - unit = 'second' - config.set(rule_recent + ['time'], value=unit) - - if config.exists(rule_tcp_flags): - tmp = config.return_value(rule_tcp_flags) - config.delete(rule_tcp_flags) - for flag in tmp.split(","): - if flag[0] == '!': - config.set(rule_tcp_flags + ['not', flag[1:].lower()]) - else: - config.set(rule_tcp_flags + [flag.lower()]) - - if config.exists(base + ['ipv6-name', name, 'rule', rule, 'protocol']): - tmp = config.return_value(base + ['ipv6-name', name, 'rule', rule, 'protocol']) - if tmp == 'icmpv6': - config.set(base + ['ipv6-name', name, 'rule', rule, 'protocol'], value='ipv6-icmp') - - if config.exists(rule_icmp + ['type']): - tmp = config.return_value(rule_icmp + ['type']) - type_code_match = re.match(r'^(\d+)(?:/(\d+))?$', tmp) - - if type_code_match: - config.set(rule_icmp + ['type'], value=type_code_match[1]) - if type_code_match[2]: - config.set(rule_icmp + ['code'], value=type_code_match[2]) - elif tmp in icmpv6_remove: - config.delete(rule_icmp + ['type']) - elif tmp in icmpv6_translations: - translate = icmpv6_translations[tmp] - if isinstance(translate, str): + while config.exists(base + ['name', new_name]): + replacement_string = replacement_string + "_" + new_name = name.replace('+', replacement_string) + config.copy(base + ['name', name], base + ['name', new_name]) + config.delete(base + ['name', name]) + + if config.exists(base + ['ipv6-name']): + for name in config.list_nodes(base + ['ipv6-name']): + name_description = base + ['ipv6-name', name, 'description'] + if config.exists(name_description): + tmp = config.return_value(name_description) + config.set(name_description, value=tmp[:max_len_description]) + + if not config.exists(base + ['ipv6-name', name, 'rule']): + continue + + for rule in config.list_nodes(base + ['ipv6-name', name, 'rule']): + rule_description = base + ['ipv6-name', name, 'rule', rule, 'description'] + if config.exists(rule_description): + tmp = config.return_value(rule_description) + config.set(rule_description, value=tmp[:max_len_description]) + + rule_recent = base + ['ipv6-name', name, 'rule', rule, 'recent'] + rule_time = base + ['ipv6-name', name, 'rule', rule, 'time'] + rule_tcp_flags = base + ['ipv6-name', name, 'rule', rule, 'tcp', 'flags'] + rule_icmp = base + ['ipv6-name', name, 'rule', rule, 'icmpv6'] + + if config.exists(rule_time + ['monthdays']): + config.delete(rule_time + ['monthdays']) + + if config.exists(rule_time + ['utc']): + config.delete(rule_time + ['utc']) + + if config.exists(rule_recent + ['time']): + tmp = int(config.return_value(rule_recent + ['time'])) + unit = 'minute' + if tmp > 600: + unit = 'hour' + elif tmp < 10: + unit = 'second' + config.set(rule_recent + ['time'], value=unit) + + if config.exists(rule_tcp_flags): + tmp = config.return_value(rule_tcp_flags) + config.delete(rule_tcp_flags) + for flag in tmp.split(","): + if flag[0] == '!': + config.set(rule_tcp_flags + ['not', flag[1:].lower()]) + else: + config.set(rule_tcp_flags + [flag.lower()]) + + if config.exists(base + ['ipv6-name', name, 'rule', rule, 'protocol']): + tmp = config.return_value(base + ['ipv6-name', name, 'rule', rule, 'protocol']) + if tmp == 'icmpv6': + config.set(base + ['ipv6-name', name, 'rule', rule, 'protocol'], value='ipv6-icmp') + + if config.exists(rule_icmp + ['type']): + tmp = config.return_value(rule_icmp + ['type']) + type_code_match = re.match(r'^(\d+)(?:/(\d+))?$', tmp) + + if type_code_match: + config.set(rule_icmp + ['type'], value=type_code_match[1]) + if type_code_match[2]: + config.set(rule_icmp + ['code'], value=type_code_match[2]) + elif tmp in icmpv6_remove: config.delete(rule_icmp + ['type']) - config.set(rule_icmp + ['type-name'], value=translate) - elif isinstance(translate, list): - config.set(rule_icmp + ['type'], value=translate[0]) - config.set(rule_icmp + ['code'], value=translate[1]) - else: - config.rename(rule_icmp + ['type'], 'type-name') - - for direction in ['destination', 'source']: - if config.exists(base + ['ipv6-name', name, 'rule', rule, direction]): - if config.exists(base + ['ipv6-name', name, 'rule', rule, direction, 'group']) and v6_found: - for group_type in config.list_nodes(base + ['ipv6-name', name, 'rule', rule, direction, 'group']): - group_name = config.return_value(base + ['ipv6-name', name, 'rule', rule, direction, 'group', group_type]) - if '+' in group_name: - if group_name[0] == "!": - new_group_name = "!" + translated_dict[group_name[1:]] - else: - new_group_name = translated_dict[group_name] - config.set(base + ['ipv6-name', name, 'rule', rule, direction, 'group', group_type], value=new_group_name) - - pg_base = base + ['ipv6-name', name, 'rule', rule, direction, 'group', 'port-group'] - proto_base = base + ['ipv6-name', name, 'rule', rule, 'protocol'] - if config.exists(pg_base) and not config.exists(proto_base): - config.set(proto_base, value='tcp_udp') - - if '+' in name: - replacement_string = "_" - new_name = name.replace('+', replacement_string) - while config.exists(base + ['ipv6-name', new_name]): - replacement_string = replacement_string + "_" + elif tmp in icmpv6_translations: + translate = icmpv6_translations[tmp] + if isinstance(translate, str): + config.delete(rule_icmp + ['type']) + config.set(rule_icmp + ['type-name'], value=translate) + elif isinstance(translate, list): + config.set(rule_icmp + ['type'], value=translate[0]) + config.set(rule_icmp + ['code'], value=translate[1]) + else: + config.rename(rule_icmp + ['type'], 'type-name') + + for direction in ['destination', 'source']: + if config.exists(base + ['ipv6-name', name, 'rule', rule, direction]): + if config.exists(base + ['ipv6-name', name, 'rule', rule, direction, 'group']) and v6_found: + for group_type in config.list_nodes(base + ['ipv6-name', name, 'rule', rule, direction, 'group']): + group_name = config.return_value(base + ['ipv6-name', name, 'rule', rule, direction, 'group', group_type]) + if '+' in group_name: + if group_name[0] == "!": + new_group_name = "!" + translated_dict[group_name[1:]] + else: + new_group_name = translated_dict[group_name] + config.set(base + ['ipv6-name', name, 'rule', rule, direction, 'group', group_type], value=new_group_name) + + pg_base = base + ['ipv6-name', name, 'rule', rule, direction, 'group', 'port-group'] + proto_base = base + ['ipv6-name', name, 'rule', rule, 'protocol'] + if config.exists(pg_base) and not config.exists(proto_base): + config.set(proto_base, value='tcp_udp') + + if '+' in name: + replacement_string = "_" new_name = name.replace('+', replacement_string) - config.copy(base + ['ipv6-name', name], base + ['ipv6-name', new_name]) - config.delete(base + ['ipv6-name', name]) -try: - with open(file_name, 'w') as f: - f.write(config.to_string()) -except OSError as e: - print("Failed to save the modified config: {}".format(e)) - exit(1) + while config.exists(base + ['ipv6-name', new_name]): + replacement_string = replacement_string + "_" + new_name = name.replace('+', replacement_string) + config.copy(base + ['ipv6-name', name], base + ['ipv6-name', new_name]) + config.delete(base + ['ipv6-name', name]) diff --git a/src/migration-scripts/firewall/7-to-8 b/src/migration-scripts/firewall/7-to-8 old mode 100755 new mode 100644 index 17af0f355..b8bcc52cc --- a/src/migration-scripts/firewall/7-to-8 +++ b/src/migration-scripts/firewall/7-to-8 @@ -1,101 +1,81 @@ -#!/usr/bin/env python3 +# Copyright 2022-2024 VyOS maintainers and contributors <maintainers@vyos.io> # -# Copyright (C) 2022-2024 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 program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, +# 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 General Public License for more details. +# 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 General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. # T2199: Migrate interface firewall nodes to firewall interfaces <ifname> <direction> name/ipv6-name <name> # T2199: Migrate zone-policy to firewall node -from sys import argv -from sys import exit - from vyos.configtree import ConfigTree -if len(argv) < 2: - print("Must specify file name!") - exit(1) - -file_name = argv[1] - -with open(file_name, 'r') as f: - config_file = f.read() - base = ['firewall'] zone_base = ['zone-policy'] -config = ConfigTree(config_file) - -if not config.exists(base) and not config.exists(zone_base): - # Nothing to do - exit(0) def migrate_interface(config, iftype, ifname, vif=None, vifs=None, vifc=None): if_path = ['interfaces', iftype, ifname] ifname_full = ifname if vif: if_path += ['vif', vif] ifname_full = f'{ifname}.{vif}' elif vifs: if_path += ['vif-s', vifs] ifname_full = f'{ifname}.{vifs}' if vifc: if_path += ['vif-c', vifc] ifname_full = f'{ifname}.{vifs}.{vifc}' if not config.exists(if_path + ['firewall']): return if not config.exists(['firewall', 'interface']): config.set(['firewall', 'interface']) config.set_tag(['firewall', 'interface']) config.copy(if_path + ['firewall'], ['firewall', 'interface', ifname_full]) config.delete(if_path + ['firewall']) -for iftype in config.list_nodes(['interfaces']): - for ifname in config.list_nodes(['interfaces', iftype]): - migrate_interface(config, iftype, ifname) - - if config.exists(['interfaces', iftype, ifname, 'vif']): - for vif in config.list_nodes(['interfaces', iftype, ifname, 'vif']): - migrate_interface(config, iftype, ifname, vif=vif) - - if config.exists(['interfaces', iftype, ifname, 'vif-s']): - for vifs in config.list_nodes(['interfaces', iftype, ifname, 'vif-s']): - migrate_interface(config, iftype, ifname, vifs=vifs) - - if config.exists(['interfaces', iftype, ifname, 'vif-s', vifs, 'vif-c']): - for vifc in config.list_nodes(['interfaces', iftype, ifname, 'vif-s', vifs, 'vif-c']): - migrate_interface(config, iftype, ifname, vifs=vifs, vifc=vifc) - -if config.exists(zone_base + ['zone']): - config.set(['firewall', 'zone']) - config.set_tag(['firewall', 'zone']) - - for zone in config.list_nodes(zone_base + ['zone']): - if 'interface' in config.list_nodes(zone_base + ['zone', zone]): - for iface in config.return_values(zone_base + ['zone', zone, 'interface']): - if '+' in iface: - config.delete_value(zone_base + ['zone', zone, 'interface'], value=iface) - iface = iface.replace('+', '*') - config.set(zone_base + ['zone', zone, 'interface'], value=iface, replace=False) - config.copy(zone_base + ['zone', zone], ['firewall', 'zone', zone]) - config.delete(zone_base) +def migrate(config: ConfigTree) -> None: + if not config.exists(base) and not config.exists(zone_base): + # Nothing to do + return -try: - with open(file_name, 'w') as f: - f.write(config.to_string()) -except OSError as e: - print("Failed to save the modified config: {}".format(e)) - exit(1) + for iftype in config.list_nodes(['interfaces']): + for ifname in config.list_nodes(['interfaces', iftype]): + migrate_interface(config, iftype, ifname) + + if config.exists(['interfaces', iftype, ifname, 'vif']): + for vif in config.list_nodes(['interfaces', iftype, ifname, 'vif']): + migrate_interface(config, iftype, ifname, vif=vif) + + if config.exists(['interfaces', iftype, ifname, 'vif-s']): + for vifs in config.list_nodes(['interfaces', iftype, ifname, 'vif-s']): + migrate_interface(config, iftype, ifname, vifs=vifs) + + if config.exists(['interfaces', iftype, ifname, 'vif-s', vifs, 'vif-c']): + for vifc in config.list_nodes(['interfaces', iftype, ifname, 'vif-s', vifs, 'vif-c']): + migrate_interface(config, iftype, ifname, vifs=vifs, vifc=vifc) + + if config.exists(zone_base + ['zone']): + config.set(['firewall', 'zone']) + config.set_tag(['firewall', 'zone']) + + for zone in config.list_nodes(zone_base + ['zone']): + if 'interface' in config.list_nodes(zone_base + ['zone', zone]): + for iface in config.return_values(zone_base + ['zone', zone, 'interface']): + if '+' in iface: + config.delete_value(zone_base + ['zone', zone, 'interface'], value=iface) + iface = iface.replace('+', '*') + config.set(zone_base + ['zone', zone, 'interface'], value=iface, replace=False) + config.copy(zone_base + ['zone', zone], ['firewall', 'zone', zone]) + config.delete(zone_base) \ No newline at end of file diff --git a/src/migration-scripts/firewall/8-to-9 b/src/migration-scripts/firewall/8-to-9 old mode 100755 new mode 100644 index 6e019beb2..3c9e84662 --- a/src/migration-scripts/firewall/8-to-9 +++ b/src/migration-scripts/firewall/8-to-9 @@ -1,88 +1,68 @@ -#!/usr/bin/env python3 +# Copyright 2022-2024 VyOS maintainers and contributors <maintainers@vyos.io> # -# Copyright (C) 2022-2024 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 program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, +# 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 General Public License for more details. +# 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 General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. # T4780: Add firewall interface group # cli changes from: # set firewall [name | ipv6-name] <name> rule <number> [inbound-interface | outbound-interface] <interface_name> # To # set firewall [name | ipv6-name] <name> rule <number> [inbound-interface | outbound-interface] [interface-name | interface-group] <interface_name | interface_group> -from sys import argv -from sys import exit - from vyos.configtree import ConfigTree -if len(argv) < 2: - print("Must specify file name!") - exit(1) - -file_name = argv[1] - -with open(file_name, 'r') as f: - config_file = f.read() - base = ['firewall'] -config = ConfigTree(config_file) - -if not config.exists(base): - # Nothing to do - exit(0) - -if config.exists(base + ['name']): - for name in config.list_nodes(base + ['name']): - if not config.exists(base + ['name', name, 'rule']): - continue - - for rule in config.list_nodes(base + ['name', name, 'rule']): - rule_iiface = base + ['name', name, 'rule', rule, 'inbound-interface'] - rule_oiface = base + ['name', name, 'rule', rule, 'outbound-interface'] - - if config.exists(rule_iiface): - tmp = config.return_value(rule_iiface) - config.delete(rule_iiface) - config.set(rule_iiface + ['interface-name'], value=tmp) - - if config.exists(rule_oiface): - tmp = config.return_value(rule_oiface) - config.delete(rule_oiface) - config.set(rule_oiface + ['interface-name'], value=tmp) - - -if config.exists(base + ['ipv6-name']): - for name in config.list_nodes(base + ['ipv6-name']): - if not config.exists(base + ['ipv6-name', name, 'rule']): - continue - - for rule in config.list_nodes(base + ['ipv6-name', name, 'rule']): - rule_iiface = base + ['ipv6-name', name, 'rule', rule, 'inbound-interface'] - rule_oiface = base + ['ipv6-name', name, 'rule', rule, 'outbound-interface'] - - if config.exists(rule_iiface): - tmp = config.return_value(rule_iiface) - config.delete(rule_iiface) - config.set(rule_iiface + ['interface-name'], value=tmp) - - if config.exists(rule_oiface): - tmp = config.return_value(rule_oiface) - config.delete(rule_oiface) - config.set(rule_oiface + ['interface-name'], value=tmp) -try: - with open(file_name, 'w') as f: - f.write(config.to_string()) -except OSError as e: - print("Failed to save the modified config: {}".format(e)) - exit(1) +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + # Nothing to do + return + + if config.exists(base + ['name']): + for name in config.list_nodes(base + ['name']): + if not config.exists(base + ['name', name, 'rule']): + continue + + for rule in config.list_nodes(base + ['name', name, 'rule']): + rule_iiface = base + ['name', name, 'rule', rule, 'inbound-interface'] + rule_oiface = base + ['name', name, 'rule', rule, 'outbound-interface'] + + if config.exists(rule_iiface): + tmp = config.return_value(rule_iiface) + config.delete(rule_iiface) + config.set(rule_iiface + ['interface-name'], value=tmp) + + if config.exists(rule_oiface): + tmp = config.return_value(rule_oiface) + config.delete(rule_oiface) + config.set(rule_oiface + ['interface-name'], value=tmp) + + + if config.exists(base + ['ipv6-name']): + for name in config.list_nodes(base + ['ipv6-name']): + if not config.exists(base + ['ipv6-name', name, 'rule']): + continue + + for rule in config.list_nodes(base + ['ipv6-name', name, 'rule']): + rule_iiface = base + ['ipv6-name', name, 'rule', rule, 'inbound-interface'] + rule_oiface = base + ['ipv6-name', name, 'rule', rule, 'outbound-interface'] + + if config.exists(rule_iiface): + tmp = config.return_value(rule_iiface) + config.delete(rule_iiface) + config.set(rule_iiface + ['interface-name'], value=tmp) + + if config.exists(rule_oiface): + tmp = config.return_value(rule_oiface) + config.delete(rule_oiface) + config.set(rule_oiface + ['interface-name'], value=tmp) diff --git a/src/migration-scripts/firewall/9-to-10 b/src/migration-scripts/firewall/9-to-10 old mode 100755 new mode 100644 index ce509a731..306a53a86 --- a/src/migration-scripts/firewall/9-to-10 +++ b/src/migration-scripts/firewall/9-to-10 @@ -1,77 +1,57 @@ -#!/usr/bin/env python3 +# Copyright 2023-2024 VyOS maintainers and contributors <maintainers@vyos.io> # -# Copyright (C) 2023-2024 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 program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, +# 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 General Public License for more details. +# 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 General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. # T5050: Log options # cli changes from: # set firewall [name | ipv6-name] <name> rule <number> log-level <log_level> # To # set firewall [name | ipv6-name] <name> rule <number> log-options level <log_level> -from sys import argv -from sys import exit - from vyos.configtree import ConfigTree -if len(argv) < 2: - print("Must specify file name!") - exit(1) - -file_name = argv[1] - -with open(file_name, 'r') as f: - config_file = f.read() - base = ['firewall'] -config = ConfigTree(config_file) - -if not config.exists(base): - # Nothing to do - exit(0) - -if config.exists(base + ['name']): - for name in config.list_nodes(base + ['name']): - if not config.exists(base + ['name', name, 'rule']): - continue - - for rule in config.list_nodes(base + ['name', name, 'rule']): - log_options_base = base + ['name', name, 'rule', rule, 'log-options'] - rule_log_level = base + ['name', name, 'rule', rule, 'log-level'] - - if config.exists(rule_log_level): - tmp = config.return_value(rule_log_level) - config.delete(rule_log_level) - config.set(log_options_base + ['level'], value=tmp) - -if config.exists(base + ['ipv6-name']): - for name in config.list_nodes(base + ['ipv6-name']): - if not config.exists(base + ['ipv6-name', name, 'rule']): - continue - - for rule in config.list_nodes(base + ['ipv6-name', name, 'rule']): - log_options_base = base + ['ipv6-name', name, 'rule', rule, 'log-options'] - rule_log_level = base + ['ipv6-name', name, 'rule', rule, 'log-level'] - - if config.exists(rule_log_level): - tmp = config.return_value(rule_log_level) - config.delete(rule_log_level) - config.set(log_options_base + ['level'], value=tmp) -try: - with open(file_name, 'w') as f: - f.write(config.to_string()) -except OSError as e: - print("Failed to save the modified config: {}".format(e)) - exit(1) +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + # Nothing to do + return + + if config.exists(base + ['name']): + for name in config.list_nodes(base + ['name']): + if not config.exists(base + ['name', name, 'rule']): + continue + + for rule in config.list_nodes(base + ['name', name, 'rule']): + log_options_base = base + ['name', name, 'rule', rule, 'log-options'] + rule_log_level = base + ['name', name, 'rule', rule, 'log-level'] + + if config.exists(rule_log_level): + tmp = config.return_value(rule_log_level) + config.delete(rule_log_level) + config.set(log_options_base + ['level'], value=tmp) + + if config.exists(base + ['ipv6-name']): + for name in config.list_nodes(base + ['ipv6-name']): + if not config.exists(base + ['ipv6-name', name, 'rule']): + continue + + for rule in config.list_nodes(base + ['ipv6-name', name, 'rule']): + log_options_base = base + ['ipv6-name', name, 'rule', rule, 'log-options'] + rule_log_level = base + ['ipv6-name', name, 'rule', rule, 'log-level'] + + if config.exists(rule_log_level): + tmp = config.return_value(rule_log_level) + config.delete(rule_log_level) + config.set(log_options_base + ['level'], value=tmp) diff --git a/src/migration-scripts/flow-accounting/0-to-1 b/src/migration-scripts/flow-accounting/0-to-1 old mode 100755 new mode 100644 index 0f790fd9c..77670e3ef --- a/src/migration-scripts/flow-accounting/0-to-1 +++ b/src/migration-scripts/flow-accounting/0-to-1 @@ -1,69 +1,51 @@ -#!/usr/bin/env python3 +# Copyright 2021-2024 VyOS maintainers and contributors <maintainers@vyos.io> # -# Copyright (C) 2021 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 program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, +# 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 General Public License for more details. +# 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 General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. # T4099: flow-accounting: sync "source-ip" and "source-address" between netflow # and sflow ion CLI # T4105: flow-accounting: drop "sflow agent-address auto" -from sys import argv from vyos.configtree import ConfigTree -if len(argv) < 2: - print("Must specify file name!") - exit(1) - -file_name = argv[1] - -with open(file_name, 'r') as f: - config_file = f.read() - base = ['system', 'flow-accounting'] -config = ConfigTree(config_file) - -if not config.exists(base): - # Nothing to do - exit(0) - -# T4099 -tmp = base + ['netflow', 'source-ip'] -if config.exists(tmp): - config.rename(tmp, 'source-address') - -# T4105 -tmp = base + ['sflow', 'agent-address'] -if config.exists(tmp): - value = config.return_value(tmp) - if value == 'auto': - # delete the "auto" - config.delete(tmp) - - # 1) check if BGP router-id is set - # 2) check if OSPF router-id is set - # 3) check if OSPFv3 router-id is set - router_id = None - for protocol in ['bgp', 'ospf', 'ospfv3']: - if config.exists(['protocols', protocol, 'parameters', 'router-id']): - router_id = config.return_value(['protocols', protocol, 'parameters', 'router-id']) - break - if router_id: - config.set(tmp, value=router_id) -try: - with open(file_name, 'w') as f: - f.write(config.to_string()) -except OSError as e: - print("Failed to save the modified config: {}".format(e)) - exit(1) +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + # Nothing to do + return + + # T4099 + tmp = base + ['netflow', 'source-ip'] + if config.exists(tmp): + config.rename(tmp, 'source-address') + + # T4105 + tmp = base + ['sflow', 'agent-address'] + if config.exists(tmp): + value = config.return_value(tmp) + if value == 'auto': + # delete the "auto" + config.delete(tmp) + + # 1) check if BGP router-id is set + # 2) check if OSPF router-id is set + # 3) check if OSPFv3 router-id is set + router_id = None + for protocol in ['bgp', 'ospf', 'ospfv3']: + if config.exists(['protocols', protocol, 'parameters', 'router-id']): + router_id = config.return_value(['protocols', protocol, 'parameters', 'router-id']) + break + if router_id: + config.set(tmp, value=router_id) diff --git a/src/migration-scripts/https/0-to-1 b/src/migration-scripts/https/0-to-1 old mode 100755 new mode 100644 index 23809f5ad..52fe3f2ad --- a/src/migration-scripts/https/0-to-1 +++ b/src/migration-scripts/https/0-to-1 @@ -1,69 +1,50 @@ -#!/usr/bin/env python3 +# Copyright 202-2024 VyOS maintainers and contributors <maintainers@vyos.io> # -# Copyright (C) 2020 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 program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, +# 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 General Public License for more details. +# 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 General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. # * Move server block directives under 'virtual-host' tag node, instead of # relying on 'listen-address' tag node -import sys - from vyos.configtree import ConfigTree -if (len(sys.argv) < 2): - print("Must specify file name!") - sys.exit(1) - -file_name = sys.argv[1] - -with open(file_name, 'r') as f: - config_file = f.read() +old_base = ['service', 'https', 'listen-address'] -config = ConfigTree(config_file) +def migrate(config: ConfigTree) -> None: + if not config.exists(old_base): + # Nothing to do + return -old_base = ['service', 'https', 'listen-address'] -if not config.exists(old_base): - # Nothing to do - sys.exit(0) -else: new_base = ['service', 'https', 'virtual-host'] config.set(new_base) config.set_tag(new_base) index = 0 for addr in config.list_nodes(old_base): tag_name = f'vhost{index}' config.set(new_base + [tag_name]) config.set(new_base + [tag_name, 'listen-address'], value=addr) if config.exists(old_base + [addr, 'listen-port']): port = config.return_value(old_base + [addr, 'listen-port']) config.set(new_base + [tag_name, 'listen-port'], value=port) if config.exists(old_base + [addr, 'server-name']): names = config.return_values(old_base + [addr, 'server-name']) for name in names: config.set(new_base + [tag_name, 'server-name'], value=name, replace=False) index += 1 config.delete(old_base) - - try: - with open(file_name, 'w') as f: - f.write(config.to_string()) - except OSError as e: - print("Failed to save the modified config: {}".format(e)) - sys.exit(1) diff --git a/src/migration-scripts/https/1-to-2 b/src/migration-scripts/https/1-to-2 old mode 100755 new mode 100644 index 1a2cdc1e7..dad7ac1f0 --- a/src/migration-scripts/https/1-to-2 +++ b/src/migration-scripts/https/1-to-2 @@ -1,54 +1,35 @@ -#!/usr/bin/env python3 +# Copyright 2020-2024 VyOS maintainers and contributors <maintainers@vyos.io> # -# Copyright (C) 2020 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 program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, +# 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 General Public License for more details. +# 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 General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. # * Move 'api virtual-host' list to 'api-restrict virtual-host' so it # is owned by service_https.py -import sys - from vyos.configtree import ConfigTree -if (len(sys.argv) < 2): - print("Must specify file name!") - sys.exit(1) - -file_name = sys.argv[1] - -with open(file_name, 'r') as f: - config_file = f.read() +old_base = ['service', 'https', 'api', 'virtual-host'] -config = ConfigTree(config_file) +def migrate(config: ConfigTree) -> None: + if not config.exists(old_base): + # Nothing to do + return -old_base = ['service', 'https', 'api', 'virtual-host'] -if not config.exists(old_base): - # Nothing to do - sys.exit(0) -else: new_base = ['service', 'https', 'api-restrict', 'virtual-host'] config.set(new_base) names = config.return_values(old_base) for name in names: config.set(new_base, value=name, replace=False) config.delete(old_base) - - try: - with open(file_name, 'w') as f: - f.write(config.to_string()) - except OSError as e: - print("Failed to save the modified config: {}".format(e)) - sys.exit(1) diff --git a/src/migration-scripts/https/2-to-3 b/src/migration-scripts/https/2-to-3 old mode 100755 new mode 100644 index 2beba6d2b..1125caebf --- a/src/migration-scripts/https/2-to-3 +++ b/src/migration-scripts/https/2-to-3 @@ -1,86 +1,66 @@ -#!/usr/bin/env python3 +# Copyright 2021-2024 VyOS maintainers and contributors <maintainers@vyos.io> # -# Copyright (C) 2021 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 program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, +# 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 General Public License for more details. +# 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 General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. # * Migrate system signed certificate to use PKI -import sys - from vyos.configtree import ConfigTree from vyos.pki import create_certificate from vyos.pki import create_certificate_request from vyos.pki import create_private_key from vyos.pki import encode_certificate from vyos.pki import encode_private_key -if len(sys.argv) < 2: - print("Must specify file name!") - sys.exit(1) - -file_name = sys.argv[1] - -with open(file_name, 'r') as f: - config_file = f.read() - -config = ConfigTree(config_file) - base = ['service', 'https', 'certificates'] pki_base = ['pki'] -if not config.exists(base + ['system-generated-certificate']): - sys.exit(0) - def wrapped_pem_to_config_value(pem): out = [] for line in pem.strip().split("\n"): if not line or line.startswith("-----") or line[0] == '#': continue out.append(line) return "".join(out) -if not config.exists(pki_base + ['certificate']): - config.set(pki_base + ['certificate']) - config.set_tag(pki_base + ['certificate']) +def migrate(config: ConfigTree) -> None: + if not config.exists(base + ['system-generated-certificate']): + return -valid_days = 365 -if config.exists(base + ['system-generated-certificate', 'lifetime']): - valid_days = int(config.return_value(base + ['system-generated-certificate', 'lifetime'])) + if not config.exists(pki_base + ['certificate']): + config.set(pki_base + ['certificate']) + config.set_tag(pki_base + ['certificate']) -key = create_private_key('rsa', 2048) -subject = {'country': 'GB', 'state': 'N/A', 'locality': 'N/A', 'organization': 'VyOS', 'common_name': 'vyos'} -cert_req = create_certificate_request(subject, key, ['vyos']) -cert = create_certificate(cert_req, cert_req, key, valid_days) + valid_days = 365 + if config.exists(base + ['system-generated-certificate', 'lifetime']): + valid_days = int(config.return_value(base + ['system-generated-certificate', 'lifetime'])) -if cert: - cert_pem = encode_certificate(cert) - config.set(pki_base + ['certificate', 'generated_https', 'certificate'], value=wrapped_pem_to_config_value(cert_pem)) + key = create_private_key('rsa', 2048) + subject = {'country': 'GB', 'state': 'N/A', 'locality': 'N/A', 'organization': 'VyOS', 'common_name': 'vyos'} + cert_req = create_certificate_request(subject, key, ['vyos']) + cert = create_certificate(cert_req, cert_req, key, valid_days) -if key: - key_pem = encode_private_key(key) - config.set(pki_base + ['certificate', 'generated_https', 'private', 'key'], value=wrapped_pem_to_config_value(key_pem)) + if cert: + cert_pem = encode_certificate(cert) + config.set(pki_base + ['certificate', 'generated_https', 'certificate'], value=wrapped_pem_to_config_value(cert_pem)) -if cert and key: - config.set(base + ['certificate'], value='generated_https') -else: - print('Failed to migrate system-generated-certificate from https service') + if key: + key_pem = encode_private_key(key) + config.set(pki_base + ['certificate', 'generated_https', 'private', 'key'], value=wrapped_pem_to_config_value(key_pem)) -config.delete(base + ['system-generated-certificate']) + if cert and key: + config.set(base + ['certificate'], value='generated_https') + else: + print('Failed to migrate system-generated-certificate from https service') -try: - with open(file_name, 'w') as f: - f.write(config.to_string()) -except OSError as e: - print("Failed to save the modified config: {}".format(e)) - sys.exit(1) + config.delete(base + ['system-generated-certificate']) diff --git a/src/migration-scripts/https/3-to-4 b/src/migration-scripts/https/3-to-4 old mode 100755 new mode 100644 index b3cfca201..c01236cc6 --- a/src/migration-scripts/https/3-to-4 +++ b/src/migration-scripts/https/3-to-4 @@ -1,53 +1,34 @@ -#!/usr/bin/env python3 +# Copyright 2022-2024 VyOS maintainers and contributors <maintainers@vyos.io> # -# Copyright (C) 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 program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, +# 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 General Public License for more details. +# 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 General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. # T4768 rename node 'gql' to 'graphql'. -import sys - from vyos.configtree import ConfigTree -if len(sys.argv) < 2: - print("Must specify file name!") - sys.exit(1) - -file_name = sys.argv[1] - -with open(file_name, 'r') as f: - config_file = f.read() - -config = ConfigTree(config_file) - old_base = ['service', 'https', 'api', 'gql'] -if not config.exists(old_base): - # Nothing to do - sys.exit(0) - new_base = ['service', 'https', 'api', 'graphql'] -config.set(new_base) -nodes = config.list_nodes(old_base) -for node in nodes: - config.copy(old_base + [node], new_base + [node]) +def migrate(config: ConfigTree) -> None: + if not config.exists(old_base): + # Nothing to do + return + + config.set(new_base) -config.delete(old_base) + nodes = config.list_nodes(old_base) + for node in nodes: + config.copy(old_base + [node], new_base + [node]) -try: - with open(file_name, 'w') as f: - f.write(config.to_string()) -except OSError as e: - print("Failed to save the modified config: {}".format(e)) - sys.exit(1) + config.delete(old_base) diff --git a/src/migration-scripts/https/4-to-5 b/src/migration-scripts/https/4-to-5 old mode 100755 new mode 100644 index 0dfb6ac19..0f1c7901f --- a/src/migration-scripts/https/4-to-5 +++ b/src/migration-scripts/https/4-to-5 @@ -1,62 +1,43 @@ -#!/usr/bin/env python3 +# Copyright 2023-2024 VyOS maintainers and contributors <maintainers@vyos.io> # -# Copyright (C) 2023 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 program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, +# 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 General Public License for more details. +# 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 General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. # T5762: http: api: smoketests fail as they can not establish IPv6 connection # to uvicorn backend server, always make the UNIX domain socket the # default way of communication -import sys - from vyos.configtree import ConfigTree -if len(sys.argv) < 2: - print("Must specify file name!") - sys.exit(1) - -file_name = sys.argv[1] - -with open(file_name, 'r') as f: - config_file = f.read() - -config = ConfigTree(config_file) - base = ['service', 'https'] -if not config.exists(base): - # Nothing to do - sys.exit(0) - -# Delete "socket" CLI option - we always use UNIX domain sockets for -# NGINX <-> API server communication -if config.exists(base + ['api', 'socket']): - config.delete(base + ['api', 'socket']) - -# There is no need for an API service port, as UNIX domain sockets -# are used -if config.exists(base + ['api', 'port']): - config.delete(base + ['api', 'port']) - -# rename listen-port -> port ver virtual-host -if config.exists(base + ['virtual-host']): - for vhost in config.list_nodes(base + ['virtual-host']): - if config.exists(base + ['virtual-host', vhost, 'listen-port']): - config.rename(base + ['virtual-host', vhost, 'listen-port'], 'port') -try: - with open(file_name, 'w') as f: - f.write(config.to_string()) -except OSError as e: - print("Failed to save the modified config: {}".format(e)) - sys.exit(1) +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + # Nothing to do + return + + # Delete "socket" CLI option - we always use UNIX domain sockets for + # NGINX <-> API server communication + if config.exists(base + ['api', 'socket']): + config.delete(base + ['api', 'socket']) + + # There is no need for an API service port, as UNIX domain sockets + # are used + if config.exists(base + ['api', 'port']): + config.delete(base + ['api', 'port']) + + # rename listen-port -> port ver virtual-host + if config.exists(base + ['virtual-host']): + for vhost in config.list_nodes(base + ['virtual-host']): + if config.exists(base + ['virtual-host', vhost, 'listen-port']): + config.rename(base + ['virtual-host', vhost, 'listen-port'], 'port') diff --git a/src/migration-scripts/https/5-to-6 b/src/migration-scripts/https/5-to-6 old mode 100755 new mode 100644 index 72e9e31f7..6ef6976b6 --- a/src/migration-scripts/https/5-to-6 +++ b/src/migration-scripts/https/5-to-6 @@ -1,107 +1,89 @@ -#!/usr/bin/env python3 +# Copyright 2024 VyOS maintainers and contributors <maintainers@vyos.io> # -# Copyright (C) 2024 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 program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, +# 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 General Public License for more details. +# 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 General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. # T5886: Add support for ACME protocol (LetsEncrypt), migrate https certbot # to new "pki certificate" CLI tree # T5902: Remove virtual-host import os -import sys from vyos.configtree import ConfigTree from vyos.defaults import directories from vyos.utils.process import cmd vyos_certbot_dir = directories['certbot'] -if len(sys.argv) < 2: - print("Must specify file name!") - sys.exit(1) - -file_name = sys.argv[1] - -with open(file_name, 'r') as f: - config_file = f.read() - -config = ConfigTree(config_file) - base = ['service', 'https'] -if not config.exists(base): - # Nothing to do - sys.exit(0) - -if config.exists(base + ['certificates', 'certbot']): - # both domain-name and email must be set on CLI - ensured by previous verify() - domain_names = config.return_values(base + ['certificates', 'certbot', 'domain-name']) - email = config.return_value(base + ['certificates', 'certbot', 'email']) - config.delete(base + ['certificates', 'certbot']) - - # Set default certname based on domain-name - cert_name = 'https-' + domain_names[0].split('.')[0] - # Overwrite certname from previous certbot calls if available - # We can not use python code like os.scandir due to filesystem permissions. - # This must be run as root - certbot_live = f'{vyos_certbot_dir}/live/' # we need the trailing / - if os.path.exists(certbot_live): - tmp = cmd(f'sudo find {certbot_live} -maxdepth 1 -type d') - tmp = tmp.split() # tmp = ['/config/auth/letsencrypt/live', '/config/auth/letsencrypt/live/router.vyos.net'] - tmp.remove(certbot_live) - cert_name = tmp[0].replace(certbot_live, '') - config.set(['pki', 'certificate', cert_name, 'acme', 'email'], value=email) - config.set_tag(['pki', 'certificate']) - for domain in domain_names: - config.set(['pki', 'certificate', cert_name, 'acme', 'domain-name'], value=domain, replace=False) - - # Update Webserver certificate - config.set(base + ['certificates', 'certificate'], value=cert_name) - -if config.exists(base + ['virtual-host']): - allow_client = [] - listen_port = [] - listen_address = [] - for virtual_host in config.list_nodes(base + ['virtual-host']): - allow_path = base + ['virtual-host', virtual_host, 'allow-client', 'address'] - if config.exists(allow_path): - tmp = config.return_values(allow_path) - allow_client.extend(tmp) - - port_path = base + ['virtual-host', virtual_host, 'port'] - if config.exists(port_path): - tmp = config.return_value(port_path) - listen_port.append(tmp) - - listen_address_path = base + ['virtual-host', virtual_host, 'listen-address'] - if config.exists(listen_address_path): - tmp = config.return_value(listen_address_path) - listen_address.append(tmp) - - config.delete(base + ['virtual-host']) - for client in allow_client: - config.set(base + ['allow-client', 'address'], value=client, replace=False) - - # clear listen-address if "all" were specified - if '*' in listen_address: +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + # Nothing to do + return + + if config.exists(base + ['certificates', 'certbot']): + # both domain-name and email must be set on CLI - ensured by previous verify() + domain_names = config.return_values(base + ['certificates', 'certbot', 'domain-name']) + email = config.return_value(base + ['certificates', 'certbot', 'email']) + config.delete(base + ['certificates', 'certbot']) + + # Set default certname based on domain-name + cert_name = 'https-' + domain_names[0].split('.')[0] + # Overwrite certname from previous certbot calls if available + # We can not use python code like os.scandir due to filesystem permissions. + # This must be run as root + certbot_live = f'{vyos_certbot_dir}/live/' # we need the trailing / + if os.path.exists(certbot_live): + tmp = cmd(f'sudo find {certbot_live} -maxdepth 1 -type d') + tmp = tmp.split() # tmp = ['/config/auth/letsencrypt/live', '/config/auth/letsencrypt/live/router.vyos.net'] + tmp.remove(certbot_live) + cert_name = tmp[0].replace(certbot_live, '') + + config.set(['pki', 'certificate', cert_name, 'acme', 'email'], value=email) + config.set_tag(['pki', 'certificate']) + for domain in domain_names: + config.set(['pki', 'certificate', cert_name, 'acme', 'domain-name'], value=domain, replace=False) + + # Update Webserver certificate + config.set(base + ['certificates', 'certificate'], value=cert_name) + + if config.exists(base + ['virtual-host']): + allow_client = [] + listen_port = [] listen_address = [] - for address in listen_address: - config.set(base + ['listen-address'], value=address, replace=False) - -try: - with open(file_name, 'w') as f: - f.write(config.to_string()) -except OSError as e: - print("Failed to save the modified config: {}".format(e)) - sys.exit(1) + for virtual_host in config.list_nodes(base + ['virtual-host']): + allow_path = base + ['virtual-host', virtual_host, 'allow-client', 'address'] + if config.exists(allow_path): + tmp = config.return_values(allow_path) + allow_client.extend(tmp) + + port_path = base + ['virtual-host', virtual_host, 'port'] + if config.exists(port_path): + tmp = config.return_value(port_path) + listen_port.append(tmp) + + listen_address_path = base + ['virtual-host', virtual_host, 'listen-address'] + if config.exists(listen_address_path): + tmp = config.return_value(listen_address_path) + listen_address.append(tmp) + + config.delete(base + ['virtual-host']) + for client in allow_client: + config.set(base + ['allow-client', 'address'], value=client, replace=False) + + # clear listen-address if "all" were specified + if '*' in listen_address: + listen_address = [] + for address in listen_address: + config.set(base + ['listen-address'], value=address, replace=False) diff --git a/src/migration-scripts/ids/0-to-1 b/src/migration-scripts/ids/0-to-1 old mode 100755 new mode 100644 index 8b7850a1a..1b963e839 --- a/src/migration-scripts/ids/0-to-1 +++ b/src/migration-scripts/ids/0-to-1 @@ -1,56 +1,38 @@ -#!/usr/bin/env python3 +# Copyright 2022-2024 VyOS maintainers and contributors <maintainers@vyos.io> # -# Copyright (C) 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 program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, +# 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 General Public License for more details. +# 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 General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. -from sys import argv -from sys import exit +# T4557: Migrate threshold and add new threshold types from vyos.configtree import ConfigTree -if len(argv) < 2: - print("Must specify file name!") - exit(1) - -file_name = argv[1] - -with open(file_name, 'r') as f: - config_file = f.read() - base = ['service', 'ids', 'ddos-protection'] -config = ConfigTree(config_file) - -if not config.exists(base + ['threshold']): - # Nothing to do - exit(0) -else: - if config.exists(base + ['threshold', 'fps']): - tmp = config.return_value(base + ['threshold', 'fps']) - config.delete(base + ['threshold', 'fps']) - config.set(base + ['threshold', 'general', 'fps'], value=tmp) - if config.exists(base + ['threshold', 'mbps']): - tmp = config.return_value(base + ['threshold', 'mbps']) - config.delete(base + ['threshold', 'mbps']) - config.set(base + ['threshold', 'general', 'mbps'], value=tmp) - if config.exists(base + ['threshold', 'pps']): - tmp = config.return_value(base + ['threshold', 'pps']) - config.delete(base + ['threshold', 'pps']) - config.set(base + ['threshold', 'general', 'pps'], value=tmp) -try: - with open(file_name, 'w') as f: - f.write(config.to_string()) -except OSError as e: - print(f'Failed to save the modified config: {e}') - exit(1) +def migrate(config: ConfigTree) -> None: + if not config.exists(base + ['threshold']): + # Nothing to do + return + else: + if config.exists(base + ['threshold', 'fps']): + tmp = config.return_value(base + ['threshold', 'fps']) + config.delete(base + ['threshold', 'fps']) + config.set(base + ['threshold', 'general', 'fps'], value=tmp) + if config.exists(base + ['threshold', 'mbps']): + tmp = config.return_value(base + ['threshold', 'mbps']) + config.delete(base + ['threshold', 'mbps']) + config.set(base + ['threshold', 'general', 'mbps'], value=tmp) + if config.exists(base + ['threshold', 'pps']): + tmp = config.return_value(base + ['threshold', 'pps']) + config.delete(base + ['threshold', 'pps']) + config.set(base + ['threshold', 'general', 'pps'], value=tmp) diff --git a/src/migration-scripts/interfaces/0-to-1 b/src/migration-scripts/interfaces/0-to-1 old mode 100755 new mode 100644 index 25f6842eb..7c135e76e --- a/src/migration-scripts/interfaces/0-to-1 +++ b/src/migration-scripts/interfaces/0-to-1 @@ -1,118 +1,113 @@ -#!/usr/bin/env python3 +# Copyright 2019-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# 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 <http://www.gnu.org/licenses/>. # Change syntax of bridge interface # - move interface based bridge-group to actual bridge (de-nest) # - make stp and igmp-snooping nodes valueless # https://vyos.dev/T1556 -import sys from vyos.configtree import ConfigTree def migrate_bridge(config, tree, intf): # check if bridge-group exists tree_bridge = tree + ['bridge-group'] if config.exists(tree_bridge): bridge = config.return_value(tree_bridge + ['bridge']) # create new bridge member interface config.set(base + [bridge, 'member', 'interface', intf]) # format as tag node to avoid loading problems config.set_tag(base + [bridge, 'member', 'interface']) # cost: migrate if configured tree_cost = tree + ['bridge-group', 'cost'] if config.exists(tree_cost): cost = config.return_value(tree_cost) # set new node config.set(base + [bridge, 'member', 'interface', intf, 'cost'], value=cost) # priority: migrate if configured tree_priority = tree + ['bridge-group', 'priority'] if config.exists(tree_priority): priority = config.return_value(tree_priority) # set new node config.set(base + [bridge, 'member', 'interface', intf, 'priority'], value=priority) # Delete the old bridge-group assigned to an interface config.delete(tree_bridge) -if __name__ == '__main__': - if len(sys.argv) < 2: - print("Must specify file name!") - sys.exit(1) - - file_name = sys.argv[1] - - with open(file_name, 'r') as f: - config_file = f.read() - - config = ConfigTree(config_file) +def migrate(config: ConfigTree) -> None: base = ['interfaces', 'bridge'] if not config.exists(base): # Nothing to do - sys.exit(0) - else: - # - # make stp and igmp-snooping nodes valueless - # - for br in config.list_nodes(base): - # STP: check if enabled - if config.exists(base + [br, 'stp']): - stp_val = config.return_value(base + [br, 'stp']) - # STP: delete node with old syntax - config.delete(base + [br, 'stp']) - # STP: set new node - if enabled - if stp_val == "true": - config.set(base + [br, 'stp'], value=None) - - # igmp-snooping: check if enabled - if config.exists(base + [br, 'igmp-snooping', 'querier']): - igmp_val = config.return_value(base + [br, 'igmp-snooping', 'querier']) - # igmp-snooping: delete node with old syntax - config.delete(base + [br, 'igmp-snooping', 'querier']) - # igmp-snooping: set new node - if enabled - if igmp_val == "enable": - config.set(base + [br, 'igmp', 'querier'], value=None) - - # - # move interface based bridge-group to actual bridge (de-nest) - # - bridge_types = ['bonding', 'ethernet', 'l2tpv3', 'openvpn', 'vxlan', 'wireless'] - for type in bridge_types: - if not config.exists(['interfaces', type]): - continue - - for interface in config.list_nodes(['interfaces', type]): - # check if bridge-group exists - bridge_group = ['interfaces', type, interface] - if config.exists(bridge_group + ['bridge-group']): - migrate_bridge(config, bridge_group, interface) - - # We also need to migrate VLAN interfaces - vlan_base = ['interfaces', type, interface, 'vif'] - if config.exists(vlan_base): - for vlan in config.list_nodes(vlan_base): - intf = "{}.{}".format(interface, vlan) - migrate_bridge(config, vlan_base + [vlan], intf) - - # And then we have service VLANs (vif-s) interfaces - vlan_base = ['interfaces', type, interface, 'vif-s'] - if config.exists(vlan_base): - for vif_s in config.list_nodes(vlan_base): - intf = "{}.{}".format(interface, vif_s) - migrate_bridge(config, vlan_base + [vif_s], intf) - - # Every service VLAN can have multiple customer VLANs (vif-c) - vlan_c = ['interfaces', type, interface, 'vif-s', vif_s, 'vif-c'] - if config.exists(vlan_c): - for vif_c in config.list_nodes(vlan_c): - intf = "{}.{}.{}".format(interface, vif_s, vif_c) - migrate_bridge(config, vlan_c + [vif_c], intf) - - try: - with open(file_name, 'w') as f: - f.write(config.to_string()) - except OSError as e: - print("Failed to save the modified config: {}".format(e)) - sys.exit(1) + return + + # + # make stp and igmp-snooping nodes valueless + # + for br in config.list_nodes(base): + # STP: check if enabled + if config.exists(base + [br, 'stp']): + stp_val = config.return_value(base + [br, 'stp']) + # STP: delete node with old syntax + config.delete(base + [br, 'stp']) + # STP: set new node - if enabled + if stp_val == "true": + config.set(base + [br, 'stp'], value=None) + + # igmp-snooping: check if enabled + if config.exists(base + [br, 'igmp-snooping', 'querier']): + igmp_val = config.return_value(base + [br, 'igmp-snooping', 'querier']) + # igmp-snooping: delete node with old syntax + config.delete(base + [br, 'igmp-snooping', 'querier']) + # igmp-snooping: set new node - if enabled + if igmp_val == "enable": + config.set(base + [br, 'igmp', 'querier'], value=None) + + # + # move interface based bridge-group to actual bridge (de-nest) + # + bridge_types = ['bonding', 'ethernet', 'l2tpv3', 'openvpn', 'vxlan', 'wireless'] + for type in bridge_types: + if not config.exists(['interfaces', type]): + continue + + for interface in config.list_nodes(['interfaces', type]): + # check if bridge-group exists + bridge_group = ['interfaces', type, interface] + if config.exists(bridge_group + ['bridge-group']): + migrate_bridge(config, bridge_group, interface) + + # We also need to migrate VLAN interfaces + vlan_base = ['interfaces', type, interface, 'vif'] + if config.exists(vlan_base): + for vlan in config.list_nodes(vlan_base): + intf = "{}.{}".format(interface, vlan) + migrate_bridge(config, vlan_base + [vlan], intf) + + # And then we have service VLANs (vif-s) interfaces + vlan_base = ['interfaces', type, interface, 'vif-s'] + if config.exists(vlan_base): + for vif_s in config.list_nodes(vlan_base): + intf = "{}.{}".format(interface, vif_s) + migrate_bridge(config, vlan_base + [vif_s], intf) + + # Every service VLAN can have multiple customer VLANs (vif-c) + vlan_c = ['interfaces', type, interface, 'vif-s', vif_s, 'vif-c'] + if config.exists(vlan_c): + for vif_c in config.list_nodes(vlan_c): + intf = "{}.{}.{}".format(interface, vif_s, vif_c) + migrate_bridge(config, vlan_c + [vif_c], intf) diff --git a/src/migration-scripts/interfaces/1-to-2 b/src/migration-scripts/interfaces/1-to-2 old mode 100755 new mode 100644 index c95623c2b..ebf02b028 --- a/src/migration-scripts/interfaces/1-to-2 +++ b/src/migration-scripts/interfaces/1-to-2 @@ -1,63 +1,59 @@ -#!/usr/bin/env python3 +# Copyright 2019-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# 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 <http://www.gnu.org/licenses/>. # Change syntax of bond interface # - move interface based bond-group to actual bond (de-nest) # https://vyos.dev/T1614 -import sys from vyos.configtree import ConfigTree -if len(sys.argv) < 2: - print("Must specify file name!") - sys.exit(1) - -file_name = sys.argv[1] - -with open(file_name, 'r') as f: - config_file = f.read() - -config = ConfigTree(config_file) base = ['interfaces', 'bonding'] -if not config.exists(base): - # Nothing to do - sys.exit(0) -else: +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + # Nothing to do + return + # # move interface based bond-group to actual bond (de-nest) # for intf in config.list_nodes(['interfaces', 'ethernet']): # check if bond-group exists if config.exists(['interfaces', 'ethernet', intf, 'bond-group']): # get configured bond interface bond = config.return_value(['interfaces', 'ethernet', intf, 'bond-group']) # delete old interface asigned (nested) bond group config.delete(['interfaces', 'ethernet', intf, 'bond-group']) # create new bond member interface config.set(base + [bond, 'member', 'interface'], value=intf, replace=False) # # some combinations were allowed in the past from a CLI perspective # but the kernel overwrote them - remove from CLI to not confuse the users. # In addition new consitency checks are in place so users can't repeat the # mistake. One of those nice issues is https://vyos.dev/T532 for bond in config.list_nodes(base): if config.exists(base + [bond, 'arp-monitor', 'interval']) and config.exists(base + [bond, 'mode']): mode = config.return_value(base + [bond, 'mode']) if mode in ['802.3ad', 'transmit-load-balance', 'adaptive-load-balance']: intvl = int(config.return_value(base + [bond, 'arp-monitor', 'interval'])) if intvl > 0: # this is not allowed and the linux kernel replies with: # option arp_interval: mode dependency failed, not supported in mode 802.3ad(4) # option arp_interval: mode dependency failed, not supported in mode balance-alb(6) # option arp_interval: mode dependency failed, not supported in mode balance-tlb(5) # # so we simply disable arp_interval by setting it to 0 and miimon will take care about the link config.set(base + [bond, 'arp-monitor', 'interval'], value='0') - - try: - with open(file_name, 'w') as f: - f.write(config.to_string()) - except OSError as e: - print("Failed to save the modified config: {}".format(e)) - sys.exit(1) diff --git a/src/migration-scripts/interfaces/10-to-11 b/src/migration-scripts/interfaces/10-to-11 old mode 100755 new mode 100644 index cafaa3fa4..8a562f2d0 --- a/src/migration-scripts/interfaces/10-to-11 +++ b/src/migration-scripts/interfaces/10-to-11 @@ -1,55 +1,38 @@ -#!/usr/bin/env python3 +# Copyright 2020-2024 VyOS maintainers and contributors <maintainers@vyos.io> # -# Copyright (C) 2020 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 program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, +# 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 General Public License for more details. +# 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 General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. # rename WWAN (wirelessmodem) serial interface from non persistent ttyUSB2 to # a bus like name, e.g. "usb0b1.3p1.3" import os -from sys import exit, argv from vyos.configtree import ConfigTree -if __name__ == '__main__': - if len(argv) < 2: - print("Must specify file name!") - exit(1) - - file_name = argv[1] - with open(file_name, 'r') as f: - config_file = f.read() +base = ['interfaces', 'wirelessmodem'] - config = ConfigTree(config_file) - base = ['interfaces', 'wirelessmodem'] +def migrate(config: ConfigTree) -> None: if not config.exists(base): # Nothing to do - exit(0) + return for wwan in config.list_nodes(base): if config.exists(base + [wwan, 'device']): device = config.return_value(base + [wwan, 'device']) for root, dirs, files in os.walk('/dev/serial/by-bus'): for file in files: device_file = os.path.realpath(os.path.join(root, file)) if os.path.basename(device_file) == device: config.set(base + [wwan, 'device'], value=file, replace=True) - - try: - with open(file_name, 'w') as f: - f.write(config.to_string()) - except OSError as e: - print("Failed to save the modified config: {}".format(e)) - exit(1) diff --git a/src/migration-scripts/interfaces/11-to-12 b/src/migration-scripts/interfaces/11-to-12 old mode 100755 new mode 100644 index e9eb7f939..132cecbb7 --- a/src/migration-scripts/interfaces/11-to-12 +++ b/src/migration-scripts/interfaces/11-to-12 @@ -1,58 +1,39 @@ -#!/usr/bin/env python3 +# Copyright 2020-2024 VyOS maintainers and contributors <maintainers@vyos.io> # -# Copyright (C) 2020 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 program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, +# 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 General Public License for more details. +# 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 General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. # - rename 'dhcpv6-options prefix-delegation' from single node to a new tag node # 'dhcpv6-options pd 0' # - delete 'sla-len' from CLI - value is calculated on demand -from sys import exit, argv from vyos.configtree import ConfigTree -if __name__ == '__main__': - if len(argv) < 2: - print("Must specify file name!") - exit(1) - - file_name = argv[1] - with open(file_name, 'r') as f: - config_file = f.read() - - config = ConfigTree(config_file) - +def migrate(config: ConfigTree) -> None: for type in config.list_nodes(['interfaces']): for interface in config.list_nodes(['interfaces', type]): # cache current config tree base_path = ['interfaces', type, interface, 'dhcpv6-options'] old_base = base_path + ['prefix-delegation'] new_base = base_path + ['pd'] if config.exists(old_base): config.set(new_base) config.set_tag(new_base) config.copy(old_base, new_base + ['0']) config.delete(old_base) for pd in config.list_nodes(new_base): for tmp in config.list_nodes(new_base + [pd, 'interface']): sla_config = new_base + [pd, 'interface', tmp, 'sla-len'] if config.exists(sla_config): config.delete(sla_config) - - try: - with open(file_name, 'w') as f: - f.write(config.to_string()) - except OSError as e: - print("Failed to save the modified config: {}".format(e)) - exit(1) diff --git a/src/migration-scripts/interfaces/12-to-13 b/src/migration-scripts/interfaces/12-to-13 old mode 100755 new mode 100644 index ef1d93903..585deb898 --- a/src/migration-scripts/interfaces/12-to-13 +++ b/src/migration-scripts/interfaces/12-to-13 @@ -1,71 +1,51 @@ -#!/usr/bin/env python3 +# Copyright 2020-2024 VyOS maintainers and contributors <maintainers@vyos.io> # -# Copyright (C) 2020 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 program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, +# 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 General Public License for more details. +# 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 General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. # - T2903: Change vif-s ethertype from numeric number to literal # - 0x88a8 -> 802.1ad # - 0x8100 -> 802.1q # - T2905: Change WWAN "ondemand" node to "connect-on-demand" to have identical # CLI nodes for both types of dialer interfaces -from sys import exit, argv from vyos.configtree import ConfigTree -if __name__ == '__main__': - if len(argv) < 2: - print("Must specify file name!") - exit(1) - - file_name = argv[1] - with open(file_name, 'r') as f: - config_file = f.read() - - config = ConfigTree(config_file) - +def migrate(config: ConfigTree) -> None: # # T2903 # for type in config.list_nodes(['interfaces']): for interface in config.list_nodes(['interfaces', type]): if not config.exists(['interfaces', type, interface, 'vif-s']): continue for vif_s in config.list_nodes(['interfaces', type, interface, 'vif-s']): base_path = ['interfaces', type, interface, 'vif-s', vif_s] if config.exists(base_path + ['ethertype']): protocol = '802.1ad' tmp = config.return_value(base_path + ['ethertype']) if tmp == '0x8100': protocol = '802.1q' config.set(base_path + ['protocol'], value=protocol) config.delete(base_path + ['ethertype']) # # T2905 # wwan_base = ['interfaces', 'wirelessmodem'] if config.exists(wwan_base): for interface in config.list_nodes(wwan_base): if config.exists(wwan_base + [interface, 'ondemand']): config.rename(wwan_base + [interface, 'ondemand'], 'connect-on-demand') - - try: - with open(file_name, 'w') as f: - f.write(config.to_string()) - except OSError as e: - print("Failed to save the modified config: {}".format(e)) - exit(1) - diff --git a/src/migration-scripts/interfaces/13-to-14 b/src/migration-scripts/interfaces/13-to-14 old mode 100755 new mode 100644 index b20d8b4db..45d8e3b5f --- a/src/migration-scripts/interfaces/13-to-14 +++ b/src/migration-scripts/interfaces/13-to-14 @@ -1,59 +1,42 @@ -#!/usr/bin/env python3 +# Copyright 2020-2024 VyOS maintainers and contributors <maintainers@vyos.io> # -# Copyright (C) 2020 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 program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, +# 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 General Public License for more details. +# 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 General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. # T3043: rename Wireless interface security mode 'both' to 'wpa+wpa2' # T3043: move "system wifi-regulatory-domain" to indicidual wireless interface -from sys import exit, argv from vyos.configtree import ConfigTree -if __name__ == '__main__': - if len(argv) < 2: - print("Must specify file name!") - exit(1) - - file_name = argv[1] - with open(file_name, 'r') as f: - config_file = f.read() - - config = ConfigTree(config_file) +def migrate(config: ConfigTree) -> None: base = ['interfaces', 'wireless'] + if not config.exists(base): # Nothing to do - exit(0) + return country_code = '' cc_cli = ['system', 'wifi-regulatory-domain'] if config.exists(cc_cli): country_code = config.return_value(cc_cli) config.delete(cc_cli) for wifi in config.list_nodes(base): sec_mode = base + [wifi, 'security', 'wpa', 'mode'] if config.exists(sec_mode): mode = config.return_value(sec_mode) if mode == 'both': config.set(sec_mode, value='wpa+wpa2', replace=True) if country_code: config.set(base + [wifi, 'country-code'], value=country_code) - - try: - with open(file_name, 'w') as f: - f.write(config.to_string()) - except OSError as e: - print("Failed to save the modified config: {}".format(e)) - exit(1) diff --git a/src/migration-scripts/interfaces/14-to-15 b/src/migration-scripts/interfaces/14-to-15 old mode 100755 new mode 100644 index e21251f86..d45d59bba --- a/src/migration-scripts/interfaces/14-to-15 +++ b/src/migration-scripts/interfaces/14-to-15 @@ -1,56 +1,38 @@ -#!/usr/bin/env python3 +# Copyright 2020-2024 VyOS maintainers and contributors <maintainers@vyos.io> # -# Copyright (C) 2020 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 program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, +# 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 General Public License for more details. +# 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 General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. # T3048: remove smp-affinity node from ethernet and use tuned instead -from sys import exit, argv from vyos.configtree import ConfigTree -if __name__ == '__main__': - if len(argv) < 2: - print("Must specify file name!") - exit(1) - - file_name = argv[1] - with open(file_name, 'r') as f: - config_file = f.read() - - config = ConfigTree(config_file) +def migrate(config: ConfigTree) -> None: base = ['interfaces', 'ethernet'] if not config.exists(base): # Nothing to do - exit(0) + return migrate = False for interface in config.list_nodes(base): smp_base = base + [interface, 'smp-affinity'] # if any one interface had smp-affinity configured manually, we will # configure "system option performance" if config.exists(smp_base): if config.return_value(smp_base) != 'auto': migrate = True config.delete(smp_base) if migrate: config.set(['system', 'options', 'performance'], value='throughput') - - try: - with open(file_name, 'w') as f: - f.write(config.to_string()) - except OSError as e: - print("Failed to save the modified config: {}".format(e)) - exit(1) diff --git a/src/migration-scripts/interfaces/15-to-16 b/src/migration-scripts/interfaces/15-to-16 old mode 100755 new mode 100644 index ae3441b9f..c9abdb5f8 --- a/src/migration-scripts/interfaces/15-to-16 +++ b/src/migration-scripts/interfaces/15-to-16 @@ -1,48 +1,30 @@ -#!/usr/bin/env python3 +# Copyright 2020-2024 VyOS maintainers and contributors <maintainers@vyos.io> # -# Copyright (C) 2020 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 program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, +# 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 General Public License for more details. +# 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 General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. # remove pppoe "ipv6 enable" option -from sys import exit, argv from vyos.configtree import ConfigTree -if __name__ == '__main__': - if len(argv) < 2: - print("Must specify file name!") - exit(1) - - file_name = argv[1] - with open(file_name, 'r') as f: - config_file = f.read() - - config = ConfigTree(config_file) +def migrate(config: ConfigTree) -> None: base = ['interfaces', 'pppoe'] if not config.exists(base): # Nothing to do - exit(0) + return for interface in config.list_nodes(base): ipv6_enable = base + [interface, 'ipv6', 'enable'] if config.exists(ipv6_enable): config.delete(ipv6_enable) - - try: - with open(file_name, 'w') as f: - f.write(config.to_string()) - except OSError as e: - print("Failed to save the modified config: {}".format(e)) - exit(1) diff --git a/src/migration-scripts/interfaces/16-to-17 b/src/migration-scripts/interfaces/16-to-17 old mode 100755 new mode 100644 index 75f160686..7d241ac68 --- a/src/migration-scripts/interfaces/16-to-17 +++ b/src/migration-scripts/interfaces/16-to-17 @@ -1,52 +1,33 @@ -#!/usr/bin/env python3 +# Copyright 2020-2024 VyOS maintainers and contributors <maintainers@vyos.io> # -# Copyright (C) 2020 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 program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, +# 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 General Public License for more details. +# 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 General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. # Command line migration of port mirroring # https://vyos.dev/T3089 -import sys from vyos.configtree import ConfigTree -if __name__ == '__main__': - if len(sys.argv) < 2: - print("Must specify file name!") - sys.exit(1) - - file_name = sys.argv[1] - - with open(file_name, 'r') as f: - config_file = f.read() - - config = ConfigTree(config_file) +def migrate(config: ConfigTree) -> None: base = ['interfaces', 'ethernet'] if not config.exists(base): # Nothing to do - sys.exit(0) + return for interface in config.list_nodes(base): mirror_old_base = base + [interface, 'mirror'] if config.exists(mirror_old_base): intf = config.return_values(mirror_old_base) if config.exists(mirror_old_base): config.delete(mirror_old_base) config.set(mirror_old_base + ['ingress'],intf[0]) - - try: - with open(file_name, 'w') as f: - f.write(config.to_string()) - except OSError as e: - print("Failed to save the modified config: {}".format(e)) - sys.exit(1) diff --git a/src/migration-scripts/interfaces/17-to-18 b/src/migration-scripts/interfaces/17-to-18 old mode 100755 new mode 100644 index 51486ac37..f45695a88 --- a/src/migration-scripts/interfaces/17-to-18 +++ b/src/migration-scripts/interfaces/17-to-18 @@ -1,71 +1,52 @@ -#!/usr/bin/env python3 +# Copyright 2020-2024 VyOS maintainers and contributors <maintainers@vyos.io> # -# Copyright (C) 2020 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 program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, +# 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 General Public License for more details. +# 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 General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. # T3043: Move "system wifi-regulatory-domain" to indicidual wireless interface. # Country Code will be migratred from upper to lower case. # T3140: Relax ethernet interface offload-options -from sys import exit, argv from vyos.configtree import ConfigTree -if __name__ == '__main__': - if len(argv) < 2: - print("Must specify file name!") - exit(1) - - file_name = argv[1] - with open(file_name, 'r') as f: - config_file = f.read() - - config = ConfigTree(config_file) - +def migrate(config: ConfigTree) -> None: # T3140: Cleanup ethernet offload-options, remove on/off value and use # valueless nodes instead. eth_base = ['interfaces', 'ethernet'] if config.exists(eth_base): for eth in config.list_nodes(eth_base): offload = eth_base + [eth, 'offload-options'] if config.exists(offload): mapping = { 'generic-receive' : 'gro', 'generic-segmentation' : 'gso', 'scatter-gather' : 'sg', 'tcp-segmentation' : 'tso', 'udp-fragmentation' : 'ufo', } for k, v in mapping.items(): if config.exists(offload + [k]): tmp = config.return_value(offload + [k]) if tmp == 'on': config.set(eth_base + [eth, 'offload', v]) config.delete(offload) # T3043: WIFI country-code should be lower-case wifi_base = ['interfaces', 'wireless'] if config.exists(wifi_base): for wifi in config.list_nodes(wifi_base): ccode = wifi_base + [wifi, 'country-code'] if config.exists(ccode): tmp = config.return_value(ccode) config.set(ccode, value=tmp.lower(), replace=True) - - try: - with open(file_name, 'w') as f: - f.write(config.to_string()) - except OSError as e: - print("Failed to save the modified config: {}".format(e)) - exit(1) diff --git a/src/migration-scripts/interfaces/18-to-19 b/src/migration-scripts/interfaces/18-to-19 old mode 100755 new mode 100644 index c3209f250..ae1a07adb --- a/src/migration-scripts/interfaces/18-to-19 +++ b/src/migration-scripts/interfaces/18-to-19 @@ -1,107 +1,86 @@ -#!/usr/bin/env python3 +# Copyright 2021-2024 VyOS maintainers and contributors <maintainers@vyos.io> # -# Copyright (C) 2021 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 program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, +# 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 General Public License for more details. +# 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 General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. import os -from sys import argv -from sys import exit - from vyos.configtree import ConfigTree def replace_nat_interfaces(config, old, new): if not config.exists(['nat']): return for direction in ['destination', 'source']: conf_direction = ['nat', direction, 'rule'] if not config.exists(conf_direction): return for rule in config.list_nodes(conf_direction): conf_rule = conf_direction + [rule] if config.exists(conf_rule + ['inbound-interface']): tmp = config.return_value(conf_rule + ['inbound-interface']) if tmp == old: config.set(conf_rule + ['inbound-interface'], value=new) if config.exists(conf_rule + ['outbound-interface']): tmp = config.return_value(conf_rule + ['outbound-interface']) if tmp == old: config.set(conf_rule + ['outbound-interface'], value=new) -if __name__ == '__main__': - if len(argv) < 2: - print("Must specify file name!") - exit(1) - - file_name = argv[1] - - with open(file_name, 'r') as f: - config_file = f.read() - - config = ConfigTree(config_file) +def migrate(config: ConfigTree) -> None: base = ['interfaces', 'wirelessmodem'] if not config.exists(base): # Nothing to do - exit(0) + return new_base = ['interfaces', 'wwan'] config.set(new_base) config.set_tag(new_base) for old_interface in config.list_nodes(base): # convert usb0b1.3p1.2 device identifier and extract 1.3 usb bus id usb = config.return_value(base + [old_interface, 'device']) device = usb.split('b')[-1] busid = device.split('p')[0] for new_interface in os.listdir('/sys/class/net'): # we are only interested in interfaces starting with wwan if not new_interface.startswith('wwan'): continue device = os.readlink(f'/sys/class/net/{new_interface}/device') device = device.split(':')[0] if busid in device: config.copy(base + [old_interface], new_base + [new_interface]) replace_nat_interfaces(config, old_interface, new_interface) config.delete(base) # Now that we have copied the old wirelessmodem interfaces to wwan # we can start to migrate also individual config items. for interface in config.list_nodes(new_base): # we do no longer need the USB device name config.delete(new_base + [interface, 'device']) # set/unset DNS configuration dns = new_base + [interface, 'no-peer-dns'] if config.exists(dns): config.delete(dns) else: config.set(['system', 'name-servers-dhcp'], value=interface, replace=False) # Backup distance is now handled by DHCP option "default-route-distance" distance = dns = new_base + [interface, 'backup', 'distance'] old_default_distance = '10' if config.exists(distance): old_default_distance = config.return_value(distance) config.delete(distance) config.set(new_base + [interface, 'dhcp-options', 'default-route-distance'], value=old_default_distance) # the new wwan interface use regular IP addressing config.set(new_base + [interface, 'address'], value='dhcp') - - try: - with open(file_name, 'w') as f: - f.write(config.to_string()) - except OSError as e: - print("Failed to save the modified config: {}".format(e)) - exit(1) diff --git a/src/migration-scripts/interfaces/19-to-20 b/src/migration-scripts/interfaces/19-to-20 old mode 100755 new mode 100644 index 05abae898..7ee6302e2 --- a/src/migration-scripts/interfaces/19-to-20 +++ b/src/migration-scripts/interfaces/19-to-20 @@ -1,61 +1,41 @@ -#!/usr/bin/env python3 +# Copyright 2021-2024 VyOS maintainers and contributors <maintainers@vyos.io> # -# Copyright (C) 2021 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 program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, +# 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 General Public License for more details. +# 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 General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. -from sys import argv -from sys import exit from vyos.configtree import ConfigTree -if __name__ == '__main__': - if len(argv) < 2: - print("Must specify file name!") - exit(1) - - file_name = argv[1] - with open(file_name, 'r') as f: - config_file = f.read() - - config = ConfigTree(config_file) - +def migrate(config: ConfigTree) -> None: for type in ['tunnel', 'l2tpv3']: base = ['interfaces', type] if not config.exists(base): # Nothing to do continue for interface in config.list_nodes(base): # Migrate "interface tunnel <tunX> encapsulation gre-bridge" to gretap encap_path = base + [interface, 'encapsulation'] if type == 'tunnel' and config.exists(encap_path): tmp = config.return_value(encap_path) if tmp == 'gre-bridge': config.set(encap_path, value='gretap') # Migrate "interface tunnel|l2tpv3 <interface> local-ip" to source-address # Migrate "interface tunnel|l2tpv3 <interface> remote-ip" to remote local_ip_path = base + [interface, 'local-ip'] if config.exists(local_ip_path): config.rename(local_ip_path, 'source-address') remote_ip_path = base + [interface, 'remote-ip'] if config.exists(remote_ip_path): config.rename(remote_ip_path, 'remote') - - try: - with open(file_name, 'w') as f: - f.write(config.to_string()) - except OSError as e: - print("Failed to save the modified config: {}".format(e)) - exit(1) diff --git a/src/migration-scripts/interfaces/2-to-3 b/src/migration-scripts/interfaces/2-to-3 old mode 100755 new mode 100644 index 15c3bc8be..695dcbf7a --- a/src/migration-scripts/interfaces/2-to-3 +++ b/src/migration-scripts/interfaces/2-to-3 @@ -1,43 +1,40 @@ -#!/usr/bin/env python3 +# Copyright 2019-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# 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 <http://www.gnu.org/licenses/>. # Change syntax of openvpn encryption settings # - move cipher from encryption to encryption cipher # https://vyos.dev/T1704 -import sys from vyos.configtree import ConfigTree -if len(sys.argv) < 2: - print("Must specify file name!") - sys.exit(1) - -file_name = sys.argv[1] - -with open(file_name, 'r') as f: - config_file = f.read() - -config = ConfigTree(config_file) base = ['interfaces', 'openvpn'] -if not config.exists(base): - # Nothing to do - sys.exit(0) -else: +def migrate(config: ConfigTree) -> None: + + if not config.exists(base): + # Nothing to do + return # # move cipher from "encryption" to "encryption cipher" # for intf in config.list_nodes(['interfaces', 'openvpn']): # Check if encryption is set if config.exists(['interfaces', 'openvpn', intf, 'encryption']): # Get cipher used cipher = config.return_value(['interfaces', 'openvpn', intf, 'encryption']) # Delete old syntax config.delete(['interfaces', 'openvpn', intf, 'encryption']) # Add new syntax to config config.set(['interfaces', 'openvpn', intf, 'encryption', 'cipher'], value=cipher) - try: - with open(file_name, 'w') as f: - f.write(config.to_string()) - except OSError as e: - print("Failed to save the modified config: {}".format(e)) - sys.exit(1) diff --git a/src/migration-scripts/interfaces/20-to-21 b/src/migration-scripts/interfaces/20-to-21 old mode 100755 new mode 100644 index 05a0c7237..0b6895177 --- a/src/migration-scripts/interfaces/20-to-21 +++ b/src/migration-scripts/interfaces/20-to-21 @@ -1,125 +1,107 @@ -#!/usr/bin/env python3 +# Copyright 2021-2024 VyOS maintainers and contributors <maintainers@vyos.io> # -# Copyright (C) 2021-2024 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 program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, +# 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 General Public License for more details. +# 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 General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. # T3619: mirror Linux Kernel defaults for ethernet offloading options into VyOS # CLI. See https://vyos.dev/T3619#102254 for all the details. # T3787: Remove deprecated UDP fragmentation offloading option -from sys import argv - from vyos.ethtool import Ethtool from vyos.configtree import ConfigTree from vyos.utils.network import interface_exists -if len(argv) < 2: - print("Must specify file name!") - exit(1) - -file_name = argv[1] -with open(file_name, 'r') as f: - config_file = f.read() - base = ['interfaces', 'ethernet'] -config = ConfigTree(config_file) - -if not config.exists(base): - exit(0) - -for ifname in config.list_nodes(base): - # Bail out early if interface vanished from system - if not interface_exists(ifname): - continue - - eth = Ethtool(ifname) - - # If GRO is enabled by the Kernel - we reflect this on the CLI. If GRO is - # enabled via CLI but not supported by the NIC - we remove it from the CLI - configured = config.exists(base + [ifname, 'offload', 'gro']) - enabled, fixed = eth.get_generic_receive_offload() - if configured and fixed: - config.delete(base + [ifname, 'offload', 'gro']) - elif enabled and not fixed: - config.set(base + [ifname, 'offload', 'gro']) - - # If GSO is enabled by the Kernel - we reflect this on the CLI. If GSO is - # enabled via CLI but not supported by the NIC - we remove it from the CLI - configured = config.exists(base + [ifname, 'offload', 'gso']) - enabled, fixed = eth.get_generic_segmentation_offload() - if configured and fixed: - config.delete(base + [ifname, 'offload', 'gso']) - elif enabled and not fixed: - config.set(base + [ifname, 'offload', 'gso']) - - # If LRO is enabled by the Kernel - we reflect this on the CLI. If LRO is - # enabled via CLI but not supported by the NIC - we remove it from the CLI - configured = config.exists(base + [ifname, 'offload', 'lro']) - enabled, fixed = eth.get_large_receive_offload() - if configured and fixed: - config.delete(base + [ifname, 'offload', 'lro']) - elif enabled and not fixed: - config.set(base + [ifname, 'offload', 'lro']) - - # If SG is enabled by the Kernel - we reflect this on the CLI. If SG is - # enabled via CLI but not supported by the NIC - we remove it from the CLI - configured = config.exists(base + [ifname, 'offload', 'sg']) - enabled, fixed = eth.get_scatter_gather() - if configured and fixed: - config.delete(base + [ifname, 'offload', 'sg']) - elif enabled and not fixed: - config.set(base + [ifname, 'offload', 'sg']) - - # If TSO is enabled by the Kernel - we reflect this on the CLI. If TSO is - # enabled via CLI but not supported by the NIC - we remove it from the CLI - configured = config.exists(base + [ifname, 'offload', 'tso']) - enabled, fixed = eth.get_tcp_segmentation_offload() - if configured and fixed: - config.delete(base + [ifname, 'offload', 'tso']) - elif enabled and not fixed: - config.set(base + [ifname, 'offload', 'tso']) - - # Remove deprecated UDP fragmentation offloading option - if config.exists(base + [ifname, 'offload', 'ufo']): - config.delete(base + [ifname, 'offload', 'ufo']) - - # Also while processing the interface configuration, not all adapters support - # changing the speed and duplex settings. If the desired speed and duplex - # values do not work for the NIC driver, we change them back to the default - # value of "auto" - which will be applied if the CLI node is deleted. - speed_path = base + [ifname, 'speed'] - duplex_path = base + [ifname, 'duplex'] - # speed and duplex must always be set at the same time if not set to "auto" - if config.exists(speed_path) and config.exists(duplex_path): - speed = config.return_value(speed_path) - duplex = config.return_value(duplex_path) - if speed != 'auto' and duplex != 'auto': - if not eth.check_speed_duplex(speed, duplex): - config.delete(speed_path) - config.delete(duplex_path) - - # Also while processing the interface configuration, not all adapters support - # changing disabling flow-control - or change this setting. If disabling - # flow-control is not supported by the NIC, we remove the setting from CLI - flow_control_path = base + [ifname, 'disable-flow-control'] - if config.exists(flow_control_path): - if not eth.check_flow_control(): - config.delete(flow_control_path) -try: - with open(file_name, 'w') as f: - f.write(config.to_string()) -except OSError as e: - print("Failed to save the modified config: {}".format(e)) - exit(1) +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + return + + for ifname in config.list_nodes(base): + # Bail out early if interface vanished from system + if not interface_exists(ifname): + continue + + eth = Ethtool(ifname) + + # If GRO is enabled by the Kernel - we reflect this on the CLI. If GRO is + # enabled via CLI but not supported by the NIC - we remove it from the CLI + configured = config.exists(base + [ifname, 'offload', 'gro']) + enabled, fixed = eth.get_generic_receive_offload() + if configured and fixed: + config.delete(base + [ifname, 'offload', 'gro']) + elif enabled and not fixed: + config.set(base + [ifname, 'offload', 'gro']) + + # If GSO is enabled by the Kernel - we reflect this on the CLI. If GSO is + # enabled via CLI but not supported by the NIC - we remove it from the CLI + configured = config.exists(base + [ifname, 'offload', 'gso']) + enabled, fixed = eth.get_generic_segmentation_offload() + if configured and fixed: + config.delete(base + [ifname, 'offload', 'gso']) + elif enabled and not fixed: + config.set(base + [ifname, 'offload', 'gso']) + + # If LRO is enabled by the Kernel - we reflect this on the CLI. If LRO is + # enabled via CLI but not supported by the NIC - we remove it from the CLI + configured = config.exists(base + [ifname, 'offload', 'lro']) + enabled, fixed = eth.get_large_receive_offload() + if configured and fixed: + config.delete(base + [ifname, 'offload', 'lro']) + elif enabled and not fixed: + config.set(base + [ifname, 'offload', 'lro']) + + # If SG is enabled by the Kernel - we reflect this on the CLI. If SG is + # enabled via CLI but not supported by the NIC - we remove it from the CLI + configured = config.exists(base + [ifname, 'offload', 'sg']) + enabled, fixed = eth.get_scatter_gather() + if configured and fixed: + config.delete(base + [ifname, 'offload', 'sg']) + elif enabled and not fixed: + config.set(base + [ifname, 'offload', 'sg']) + + # If TSO is enabled by the Kernel - we reflect this on the CLI. If TSO is + # enabled via CLI but not supported by the NIC - we remove it from the CLI + configured = config.exists(base + [ifname, 'offload', 'tso']) + enabled, fixed = eth.get_tcp_segmentation_offload() + if configured and fixed: + config.delete(base + [ifname, 'offload', 'tso']) + elif enabled and not fixed: + config.set(base + [ifname, 'offload', 'tso']) + + # Remove deprecated UDP fragmentation offloading option + if config.exists(base + [ifname, 'offload', 'ufo']): + config.delete(base + [ifname, 'offload', 'ufo']) + + # Also while processing the interface configuration, not all adapters support + # changing the speed and duplex settings. If the desired speed and duplex + # values do not work for the NIC driver, we change them back to the default + # value of "auto" - which will be applied if the CLI node is deleted. + speed_path = base + [ifname, 'speed'] + duplex_path = base + [ifname, 'duplex'] + # speed and duplex must always be set at the same time if not set to "auto" + if config.exists(speed_path) and config.exists(duplex_path): + speed = config.return_value(speed_path) + duplex = config.return_value(duplex_path) + if speed != 'auto' and duplex != 'auto': + if not eth.check_speed_duplex(speed, duplex): + config.delete(speed_path) + config.delete(duplex_path) + + # Also while processing the interface configuration, not all adapters support + # changing disabling flow-control - or change this setting. If disabling + # flow-control is not supported by the NIC, we remove the setting from CLI + flow_control_path = base + [ifname, 'disable-flow-control'] + if config.exists(flow_control_path): + if not eth.check_flow_control(): + config.delete(flow_control_path) diff --git a/src/migration-scripts/interfaces/21-to-22 b/src/migration-scripts/interfaces/21-to-22 old mode 100755 new mode 100644 index 1838eb1c0..046eb10c6 --- a/src/migration-scripts/interfaces/21-to-22 +++ b/src/migration-scripts/interfaces/21-to-22 @@ -1,46 +1,29 @@ -#!/usr/bin/env python3 +# Copyright 2021-2024 VyOS maintainers and contributors <maintainers@vyos.io> # -# Copyright (C) 2021 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 program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, +# 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 General Public License for more details. +# 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 General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. -from sys import argv from vyos.configtree import ConfigTree -if len(argv) < 2: - print("Must specify file name!") - exit(1) - -file_name = argv[1] -with open(file_name, 'r') as f: - config_file = f.read() - -config = ConfigTree(config_file) -base = ['interfaces', 'tunnel'] - -if not config.exists(base): - exit(0) +def migrate(config: ConfigTree) -> None: + base = ['interfaces', 'tunnel'] -for interface in config.list_nodes(base): - path = base + [interface, 'dhcp-interface'] - if config.exists(path): - tmp = config.return_value(path) - config.delete(path) - config.set(base + [interface, 'source-interface'], value=tmp) + if not config.exists(base): + return -try: - with open(file_name, 'w') as f: - f.write(config.to_string()) -except OSError as e: - print("Failed to save the modified config: {}".format(e)) - exit(1) + for interface in config.list_nodes(base): + path = base + [interface, 'dhcp-interface'] + if config.exists(path): + tmp = config.return_value(path) + config.delete(path) + config.set(base + [interface, 'source-interface'], value=tmp) diff --git a/src/migration-scripts/interfaces/22-to-23 b/src/migration-scripts/interfaces/22-to-23 old mode 100755 new mode 100644 index 04e023e77..31f7fa2ff --- a/src/migration-scripts/interfaces/22-to-23 +++ b/src/migration-scripts/interfaces/22-to-23 @@ -1,57 +1,40 @@ -#!/usr/bin/env python3 +# Copyright 2021-2024 VyOS maintainers and contributors <maintainers@vyos.io> # -# Copyright (C) 2021-2023 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 program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, +# 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 General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. +# 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 <http://www.gnu.org/licenses/>. + # Deletes Wireguard peers if they have the same public key as the router has. -import sys + from vyos.configtree import ConfigTree from vyos.utils.network import is_wireguard_key_pair -if __name__ == '__main__': - if len(sys.argv) < 2: - print("Must specify file name!") - sys.exit(1) - - file_name = sys.argv[1] - - with open(file_name, 'r') as f: - config_file = f.read() - - config = ConfigTree(config_file) +def migrate(config: ConfigTree) -> None: base = ['interfaces', 'wireguard'] if not config.exists(base): # Nothing to do - sys.exit(0) + return + for interface in config.list_nodes(base): if not config.exists(base + [interface, 'private-key']): continue private_key = config.return_value(base + [interface, 'private-key']) interface_base = base + [interface] if config.exists(interface_base + ['peer']): for peer in config.list_nodes(interface_base + ['peer']): peer_base = interface_base + ['peer', peer] if not config.exists(peer_base + ['public-key']): continue peer_public_key = config.return_value(peer_base + ['public-key']) if not config.exists(peer_base + ['disable']) \ and is_wireguard_key_pair(private_key, peer_public_key): config.set(peer_base + ['disable']) - - try: - with open(file_name, 'w') as f: - f.write(config.to_string()) - except OSError as e: - print("Failed to save the modified config: {}".format(e)) - sys.exit(1) diff --git a/src/migration-scripts/interfaces/23-to-24 b/src/migration-scripts/interfaces/23-to-24 old mode 100755 new mode 100644 index 8b21fce51..b72ceee49 --- a/src/migration-scripts/interfaces/23-to-24 +++ b/src/migration-scripts/interfaces/23-to-24 @@ -1,145 +1,125 @@ -#!/usr/bin/env python3 +# Copyright 2021-2024 VyOS maintainers and contributors <maintainers@vyos.io> # -# Copyright (C) 2021 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 program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, +# 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 General Public License for more details. +# 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 General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. -from sys import argv -from sys import exit from vyos.configtree import ConfigTree def migrate_ospf(config, path, interface): path = path + ['ospf'] if config.exists(path): new_base = ['protocols', 'ospf', 'interface'] config.set(new_base) config.set_tag(new_base) config.copy(path, new_base + [interface]) config.delete(path) # if "ip ospf" was the only setting, we can clean out the empty # ip node afterwards if len(config.list_nodes(path[:-1])) == 0: config.delete(path[:-1]) def migrate_ospfv3(config, path, interface): path = path + ['ospfv3'] if config.exists(path): new_base = ['protocols', 'ospfv3', 'interface'] config.set(new_base) config.set_tag(new_base) config.copy(path, new_base + [interface]) config.delete(path) # if "ipv6 ospfv3" was the only setting, we can clean out the empty # ip node afterwards if len(config.list_nodes(path[:-1])) == 0: config.delete(path[:-1]) def migrate_rip(config, path, interface): path = path + ['rip'] if config.exists(path): new_base = ['protocols', 'rip', 'interface'] config.set(new_base) config.set_tag(new_base) config.copy(path, new_base + [interface]) config.delete(path) # if "ip rip" was the only setting, we can clean out the empty # ip node afterwards if len(config.list_nodes(path[:-1])) == 0: config.delete(path[:-1]) def migrate_ripng(config, path, interface): path = path + ['ripng'] if config.exists(path): new_base = ['protocols', 'ripng', 'interface'] config.set(new_base) config.set_tag(new_base) config.copy(path, new_base + [interface]) config.delete(path) # if "ipv6 ripng" was the only setting, we can clean out the empty # ip node afterwards if len(config.list_nodes(path[:-1])) == 0: config.delete(path[:-1]) -if __name__ == '__main__': - if len(argv) < 2: - print("Must specify file name!") - exit(1) - - file_name = argv[1] - with open(file_name, 'r') as f: - config_file = f.read() - - config = ConfigTree(config_file) - +def migrate(config: ConfigTree) -> None: # # Migrate "interface ethernet eth0 ip ospf" to "protocols ospf interface eth0" # for type in config.list_nodes(['interfaces']): for interface in config.list_nodes(['interfaces', type]): ip_base = ['interfaces', type, interface, 'ip'] ipv6_base = ['interfaces', type, interface, 'ipv6'] migrate_rip(config, ip_base, interface) migrate_ripng(config, ipv6_base, interface) migrate_ospf(config, ip_base, interface) migrate_ospfv3(config, ipv6_base, interface) vif_path = ['interfaces', type, interface, 'vif'] if config.exists(vif_path): for vif in config.list_nodes(vif_path): vif_ip_base = vif_path + [vif, 'ip'] vif_ipv6_base = vif_path + [vif, 'ipv6'] ifname = f'{interface}.{vif}' migrate_rip(config, vif_ip_base, ifname) migrate_ripng(config, vif_ipv6_base, ifname) migrate_ospf(config, vif_ip_base, ifname) migrate_ospfv3(config, vif_ipv6_base, ifname) vif_s_path = ['interfaces', type, interface, 'vif-s'] if config.exists(vif_s_path): for vif_s in config.list_nodes(vif_s_path): vif_s_ip_base = vif_s_path + [vif_s, 'ip'] vif_s_ipv6_base = vif_s_path + [vif_s, 'ipv6'] # vif-c interfaces MUST be migrated before their parent vif-s # interface as the migrate_*() functions delete the path! vif_c_path = ['interfaces', type, interface, 'vif-s', vif_s, 'vif-c'] if config.exists(vif_c_path): for vif_c in config.list_nodes(vif_c_path): vif_c_ip_base = vif_c_path + [vif_c, 'ip'] vif_c_ipv6_base = vif_c_path + [vif_c, 'ipv6'] ifname = f'{interface}.{vif_s}.{vif_c}' migrate_rip(config, vif_c_ip_base, ifname) migrate_ripng(config, vif_c_ipv6_base, ifname) migrate_ospf(config, vif_c_ip_base, ifname) migrate_ospfv3(config, vif_c_ipv6_base, ifname) ifname = f'{interface}.{vif_s}' migrate_rip(config, vif_s_ip_base, ifname) migrate_ripng(config, vif_s_ipv6_base, ifname) migrate_ospf(config, vif_s_ip_base, ifname) migrate_ospfv3(config, vif_s_ipv6_base, ifname) - - try: - with open(file_name, 'w') as f: - f.write(config.to_string()) - except OSError as e: - print("Failed to save the modified config: {}".format(e)) - exit(1) diff --git a/src/migration-scripts/interfaces/24-to-25 b/src/migration-scripts/interfaces/24-to-25 old mode 100755 new mode 100644 index 8fd79ecc6..9f8cc80ec --- a/src/migration-scripts/interfaces/24-to-25 +++ b/src/migration-scripts/interfaces/24-to-25 @@ -1,60 +1,41 @@ -#!/usr/bin/env python3 +# Copyright 2021-2024 VyOS maintainers and contributors <maintainers@vyos.io> # -# Copyright (C) 2021 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 program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, +# 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 General Public License for more details. +# 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 General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. # A VTI interface also requires an IPSec configuration - VyOS 1.2 supported # having a VTI interface in the CLI but no IPSec configuration - drop VTI # configuration if this is the case for VyOS 1.4 -import sys from vyos.configtree import ConfigTree -if __name__ == '__main__': - if len(sys.argv) < 2: - print("Must specify file name!") - sys.exit(1) - - file_name = sys.argv[1] - - with open(file_name, 'r') as f: - config_file = f.read() - - config = ConfigTree(config_file) +def migrate(config: ConfigTree) -> None: base = ['interfaces', 'vti'] if not config.exists(base): # Nothing to do - sys.exit(0) + return ipsec_base = ['vpn', 'ipsec', 'site-to-site', 'peer'] for interface in config.list_nodes(base): found = False if config.exists(ipsec_base): for peer in config.list_nodes(ipsec_base): if config.exists(ipsec_base + [peer, 'vti', 'bind']): tmp = config.return_value(ipsec_base + [peer, 'vti', 'bind']) if tmp == interface: # Interface was found and we no longer need to search # for it in our IPSec peers found = True break if not found: config.delete(base + [interface]) - - try: - with open(file_name, 'w') as f: - f.write(config.to_string()) - except OSError as e: - print("Failed to save the modified config: {}".format(e)) - sys.exit(1) diff --git a/src/migration-scripts/interfaces/25-to-26 b/src/migration-scripts/interfaces/25-to-26 old mode 100755 new mode 100644 index 9aa6ea5e3..7a4032d10 --- a/src/migration-scripts/interfaces/25-to-26 +++ b/src/migration-scripts/interfaces/25-to-26 @@ -1,387 +1,368 @@ -#!/usr/bin/env python3 +# Copyright 2021-2024 VyOS maintainers and contributors <maintainers@vyos.io> # -# Copyright (C) 2021-2023 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 program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, +# 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 General Public License for more details. +# 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 General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. # Migrate Wireguard to store keys in CLI # Migrate EAPoL to PKI configuration import os -import sys from vyos.configtree import ConfigTree from vyos.pki import CERT_BEGIN from vyos.pki import load_certificate from vyos.pki import load_crl from vyos.pki import load_dh_parameters from vyos.pki import load_private_key from vyos.pki import encode_certificate from vyos.pki import encode_dh_parameters from vyos.pki import encode_private_key from vyos.pki import verify_crl from vyos.utils.process import run def wrapped_pem_to_config_value(pem): out = [] for line in pem.strip().split("\n"): if not line or line.startswith("-----") or line[0] == '#': continue out.append(line) return "".join(out) def read_file_for_pki(config_auth_path): full_path = os.path.join(AUTH_DIR, config_auth_path) output = None if os.path.isfile(full_path): if not os.access(full_path, os.R_OK): run(f'sudo chmod 644 {full_path}') with open(full_path, 'r') as f: output = f.read() return output -if len(sys.argv) < 2: - print("Must specify file name!") - sys.exit(1) - -file_name = sys.argv[1] - -with open(file_name, 'r') as f: - config_file = f.read() - -config = ConfigTree(config_file) - AUTH_DIR = '/config/auth' pki_base = ['pki'] -# OpenVPN -base = ['interfaces', 'openvpn'] +def migrate(config: ConfigTree) -> None: + # OpenVPN + base = ['interfaces', 'openvpn'] + + if config.exists(base): + for interface in config.list_nodes(base): + x509_base = base + [interface, 'tls'] + pki_name = f'openvpn_{interface}' + + if config.exists(base + [interface, 'shared-secret-key-file']): + if not config.exists(pki_base + ['openvpn', 'shared-secret']): + config.set(pki_base + ['openvpn', 'shared-secret']) + config.set_tag(pki_base + ['openvpn', 'shared-secret']) + + key_file = config.return_value(base + [interface, 'shared-secret-key-file']) + key = read_file_for_pki(key_file) + key_pki_name = f'{pki_name}_shared' + + if key: + config.set(pki_base + ['openvpn', 'shared-secret', key_pki_name, 'key'], value=wrapped_pem_to_config_value(key)) + config.set(pki_base + ['openvpn', 'shared-secret', key_pki_name, 'version'], value='1') + config.set(base + [interface, 'shared-secret-key'], value=key_pki_name) + else: + print(f'Failed to migrate shared-secret-key on openvpn interface {interface}') + + config.delete(base + [interface, 'shared-secret-key-file']) + + if not config.exists(base + [interface, 'tls']): + continue + + if config.exists(base + [interface, 'tls', 'auth-file']): + if not config.exists(pki_base + ['openvpn', 'shared-secret']): + config.set(pki_base + ['openvpn', 'shared-secret']) + config.set_tag(pki_base + ['openvpn', 'shared-secret']) + + key_file = config.return_value(base + [interface, 'tls', 'auth-file']) + key = read_file_for_pki(key_file) + key_pki_name = f'{pki_name}_auth' + + if key: + config.set(pki_base + ['openvpn', 'shared-secret', key_pki_name, 'key'], value=wrapped_pem_to_config_value(key)) + config.set(pki_base + ['openvpn', 'shared-secret', key_pki_name, 'version'], value='1') + config.set(base + [interface, 'tls', 'auth-key'], value=key_pki_name) + else: + print(f'Failed to migrate auth-key on openvpn interface {interface}') + + config.delete(base + [interface, 'tls', 'auth-file']) + + if config.exists(base + [interface, 'tls', 'crypt-file']): + if not config.exists(pki_base + ['openvpn', 'shared-secret']): + config.set(pki_base + ['openvpn', 'shared-secret']) + config.set_tag(pki_base + ['openvpn', 'shared-secret']) + + key_file = config.return_value(base + [interface, 'tls', 'crypt-file']) + key = read_file_for_pki(key_file) + key_pki_name = f'{pki_name}_crypt' + + if key: + config.set(pki_base + ['openvpn', 'shared-secret', key_pki_name, 'key'], value=wrapped_pem_to_config_value(key)) + config.set(pki_base + ['openvpn', 'shared-secret', key_pki_name, 'version'], value='1') + config.set(base + [interface, 'tls', 'crypt-key'], value=key_pki_name) + else: + print(f'Failed to migrate crypt-key on openvpn interface {interface}') + + config.delete(base + [interface, 'tls', 'crypt-file']) + + ca_certs = {} + + if config.exists(x509_base + ['ca-cert-file']): + if not config.exists(pki_base + ['ca']): + config.set(pki_base + ['ca']) + config.set_tag(pki_base + ['ca']) + + cert_file = config.return_value(x509_base + ['ca-cert-file']) + cert_path = os.path.join(AUTH_DIR, cert_file) + + if os.path.isfile(cert_path): + if not os.access(cert_path, os.R_OK): + run(f'sudo chmod 644 {cert_path}') + + with open(cert_path, 'r') as f: + certs_str = f.read() + certs_data = certs_str.split(CERT_BEGIN) + index = 1 + for cert_data in certs_data[1:]: + cert = load_certificate(CERT_BEGIN + cert_data, wrap_tags=False) + + if cert: + ca_certs[f'{pki_name}_{index}'] = cert + cert_pem = encode_certificate(cert) + config.set(pki_base + ['ca', f'{pki_name}_{index}', 'certificate'], value=wrapped_pem_to_config_value(cert_pem)) + config.set(x509_base + ['ca-certificate'], value=f'{pki_name}_{index}', replace=False) + else: + print(f'Failed to migrate CA certificate on openvpn interface {interface}') + + index += 1 + else: + print(f'Failed to migrate CA certificate on openvpn interface {interface}') + + config.delete(x509_base + ['ca-cert-file']) + + if config.exists(x509_base + ['crl-file']): + if not config.exists(pki_base + ['ca']): + config.set(pki_base + ['ca']) + config.set_tag(pki_base + ['ca']) + + crl_file = config.return_value(x509_base + ['crl-file']) + crl_path = os.path.join(AUTH_DIR, crl_file) + crl = None + crl_ca_name = None + + if os.path.isfile(crl_path): + if not os.access(crl_path, os.R_OK): + run(f'sudo chmod 644 {crl_path}') + + with open(crl_path, 'r') as f: + crl_data = f.read() + crl = load_crl(crl_data, wrap_tags=False) + + for ca_name, ca_cert in ca_certs.items(): + if verify_crl(crl, ca_cert): + crl_ca_name = ca_name + break + + if crl and crl_ca_name: + crl_pem = encode_certificate(crl) + config.set(pki_base + ['ca', crl_ca_name, 'crl'], value=wrapped_pem_to_config_value(crl_pem)) + else: + print(f'Failed to migrate CRL on openvpn interface {interface}') + + config.delete(x509_base + ['crl-file']) + + if config.exists(x509_base + ['cert-file']): + if not config.exists(pki_base + ['certificate']): + config.set(pki_base + ['certificate']) + config.set_tag(pki_base + ['certificate']) + + cert_file = config.return_value(x509_base + ['cert-file']) + cert_path = os.path.join(AUTH_DIR, cert_file) + cert = None + + if os.path.isfile(cert_path): + if not os.access(cert_path, os.R_OK): + run(f'sudo chmod 644 {cert_path}') + + with open(cert_path, 'r') as f: + cert_data = f.read() + cert = load_certificate(cert_data, wrap_tags=False) + + if cert: + cert_pem = encode_certificate(cert) + config.set(pki_base + ['certificate', pki_name, 'certificate'], value=wrapped_pem_to_config_value(cert_pem)) + config.set(x509_base + ['certificate'], value=pki_name) + else: + print(f'Failed to migrate certificate on openvpn interface {interface}') + + config.delete(x509_base + ['cert-file']) + + if config.exists(x509_base + ['key-file']): + key_file = config.return_value(x509_base + ['key-file']) + key_path = os.path.join(AUTH_DIR, key_file) + key = None + + if os.path.isfile(key_path): + if not os.access(key_path, os.R_OK): + run(f'sudo chmod 644 {key_path}') + + with open(key_path, 'r') as f: + key_data = f.read() + key = load_private_key(key_data, passphrase=None, wrap_tags=False) + + if key: + key_pem = encode_private_key(key, passphrase=None) + config.set(pki_base + ['certificate', pki_name, 'private', 'key'], value=wrapped_pem_to_config_value(key_pem)) + else: + print(f'Failed to migrate private key on openvpn interface {interface}') + + config.delete(x509_base + ['key-file']) + + if config.exists(x509_base + ['dh-file']): + if not config.exists(pki_base + ['dh']): + config.set(pki_base + ['dh']) + config.set_tag(pki_base + ['dh']) + + dh_file = config.return_value(x509_base + ['dh-file']) + dh_path = os.path.join(AUTH_DIR, dh_file) + dh = None + + if os.path.isfile(dh_path): + if not os.access(dh_path, os.R_OK): + run(f'sudo chmod 644 {dh_path}') + + with open(dh_path, 'r') as f: + dh_data = f.read() + dh = load_dh_parameters(dh_data, wrap_tags=False) + + if dh: + dh_pem = encode_dh_parameters(dh) + config.set(pki_base + ['dh', pki_name, 'parameters'], value=wrapped_pem_to_config_value(dh_pem)) + config.set(x509_base + ['dh-params'], value=pki_name) + else: + print(f'Failed to migrate DH parameters on openvpn interface {interface}') + + config.delete(x509_base + ['dh-file']) + + # Wireguard + base = ['interfaces', 'wireguard'] + + if config.exists(base): + for interface in config.list_nodes(base): + private_key_path = base + [interface, 'private-key'] + + key_file = 'default' + if config.exists(private_key_path): + key_file = config.return_value(private_key_path) + + full_key_path = f'/config/auth/wireguard/{key_file}/private.key' + + if not os.path.exists(full_key_path): + print(f'Could not find wireguard private key for migration on interface "{interface}"') + continue + + with open(full_key_path, 'r') as f: + key_data = f.read().strip() + config.set(private_key_path, value=key_data) + + for peer in config.list_nodes(base + [interface, 'peer']): + config.rename(base + [interface, 'peer', peer, 'pubkey'], 'public-key') + + # Ethernet EAPoL + base = ['interfaces', 'ethernet'] + + if config.exists(base): + for interface in config.list_nodes(base): + if not config.exists(base + [interface, 'eapol']): + continue + + x509_base = base + [interface, 'eapol'] + pki_name = f'eapol_{interface}' + + if config.exists(x509_base + ['ca-cert-file']): + if not config.exists(pki_base + ['ca']): + config.set(pki_base + ['ca']) + config.set_tag(pki_base + ['ca']) + + cert_file = config.return_value(x509_base + ['ca-cert-file']) + cert_path = os.path.join(AUTH_DIR, cert_file) + cert = None + + if os.path.isfile(cert_path): + if not os.access(cert_path, os.R_OK): + run(f'sudo chmod 644 {cert_path}') + + with open(cert_path, 'r') as f: + cert_data = f.read() + cert = load_certificate(cert_data, wrap_tags=False) + + if cert: + cert_pem = encode_certificate(cert) + config.set(pki_base + ['ca', pki_name, 'certificate'], value=wrapped_pem_to_config_value(cert_pem)) + config.set(x509_base + ['ca-certificate'], value=pki_name) + else: + print(f'Failed to migrate CA certificate on eapol config for interface {interface}') + + config.delete(x509_base + ['ca-cert-file']) -if config.exists(base): - for interface in config.list_nodes(base): - x509_base = base + [interface, 'tls'] - pki_name = f'openvpn_{interface}' + if config.exists(x509_base + ['cert-file']): + if not config.exists(pki_base + ['certificate']): + config.set(pki_base + ['certificate']) + config.set_tag(pki_base + ['certificate']) - if config.exists(base + [interface, 'shared-secret-key-file']): - if not config.exists(pki_base + ['openvpn', 'shared-secret']): - config.set(pki_base + ['openvpn', 'shared-secret']) - config.set_tag(pki_base + ['openvpn', 'shared-secret']) + cert_file = config.return_value(x509_base + ['cert-file']) + cert_path = os.path.join(AUTH_DIR, cert_file) + cert = None - key_file = config.return_value(base + [interface, 'shared-secret-key-file']) - key = read_file_for_pki(key_file) - key_pki_name = f'{pki_name}_shared' + if os.path.isfile(cert_path): + if not os.access(cert_path, os.R_OK): + run(f'sudo chmod 644 {cert_path}') - if key: - config.set(pki_base + ['openvpn', 'shared-secret', key_pki_name, 'key'], value=wrapped_pem_to_config_value(key)) - config.set(pki_base + ['openvpn', 'shared-secret', key_pki_name, 'version'], value='1') - config.set(base + [interface, 'shared-secret-key'], value=key_pki_name) - else: - print(f'Failed to migrate shared-secret-key on openvpn interface {interface}') + with open(cert_path, 'r') as f: + cert_data = f.read() + cert = load_certificate(cert_data, wrap_tags=False) - config.delete(base + [interface, 'shared-secret-key-file']) + if cert: + cert_pem = encode_certificate(cert) + config.set(pki_base + ['certificate', pki_name, 'certificate'], value=wrapped_pem_to_config_value(cert_pem)) + config.set(x509_base + ['certificate'], value=pki_name) + else: + print(f'Failed to migrate certificate on eapol config for interface {interface}') - if not config.exists(base + [interface, 'tls']): - continue + config.delete(x509_base + ['cert-file']) - if config.exists(base + [interface, 'tls', 'auth-file']): - if not config.exists(pki_base + ['openvpn', 'shared-secret']): - config.set(pki_base + ['openvpn', 'shared-secret']) - config.set_tag(pki_base + ['openvpn', 'shared-secret']) - - key_file = config.return_value(base + [interface, 'tls', 'auth-file']) - key = read_file_for_pki(key_file) - key_pki_name = f'{pki_name}_auth' - - if key: - config.set(pki_base + ['openvpn', 'shared-secret', key_pki_name, 'key'], value=wrapped_pem_to_config_value(key)) - config.set(pki_base + ['openvpn', 'shared-secret', key_pki_name, 'version'], value='1') - config.set(base + [interface, 'tls', 'auth-key'], value=key_pki_name) - else: - print(f'Failed to migrate auth-key on openvpn interface {interface}') - - config.delete(base + [interface, 'tls', 'auth-file']) - - if config.exists(base + [interface, 'tls', 'crypt-file']): - if not config.exists(pki_base + ['openvpn', 'shared-secret']): - config.set(pki_base + ['openvpn', 'shared-secret']) - config.set_tag(pki_base + ['openvpn', 'shared-secret']) - - key_file = config.return_value(base + [interface, 'tls', 'crypt-file']) - key = read_file_for_pki(key_file) - key_pki_name = f'{pki_name}_crypt' - - if key: - config.set(pki_base + ['openvpn', 'shared-secret', key_pki_name, 'key'], value=wrapped_pem_to_config_value(key)) - config.set(pki_base + ['openvpn', 'shared-secret', key_pki_name, 'version'], value='1') - config.set(base + [interface, 'tls', 'crypt-key'], value=key_pki_name) - else: - print(f'Failed to migrate crypt-key on openvpn interface {interface}') - - config.delete(base + [interface, 'tls', 'crypt-file']) - - ca_certs = {} - - if config.exists(x509_base + ['ca-cert-file']): - if not config.exists(pki_base + ['ca']): - config.set(pki_base + ['ca']) - config.set_tag(pki_base + ['ca']) - - cert_file = config.return_value(x509_base + ['ca-cert-file']) - cert_path = os.path.join(AUTH_DIR, cert_file) - - if os.path.isfile(cert_path): - if not os.access(cert_path, os.R_OK): - run(f'sudo chmod 644 {cert_path}') - - with open(cert_path, 'r') as f: - certs_str = f.read() - certs_data = certs_str.split(CERT_BEGIN) - index = 1 - for cert_data in certs_data[1:]: - cert = load_certificate(CERT_BEGIN + cert_data, wrap_tags=False) - - if cert: - ca_certs[f'{pki_name}_{index}'] = cert - cert_pem = encode_certificate(cert) - config.set(pki_base + ['ca', f'{pki_name}_{index}', 'certificate'], value=wrapped_pem_to_config_value(cert_pem)) - config.set(x509_base + ['ca-certificate'], value=f'{pki_name}_{index}', replace=False) - else: - print(f'Failed to migrate CA certificate on openvpn interface {interface}') - - index += 1 - else: - print(f'Failed to migrate CA certificate on openvpn interface {interface}') - - config.delete(x509_base + ['ca-cert-file']) - - if config.exists(x509_base + ['crl-file']): - if not config.exists(pki_base + ['ca']): - config.set(pki_base + ['ca']) - config.set_tag(pki_base + ['ca']) - - crl_file = config.return_value(x509_base + ['crl-file']) - crl_path = os.path.join(AUTH_DIR, crl_file) - crl = None - crl_ca_name = None - - if os.path.isfile(crl_path): - if not os.access(crl_path, os.R_OK): - run(f'sudo chmod 644 {crl_path}') - - with open(crl_path, 'r') as f: - crl_data = f.read() - crl = load_crl(crl_data, wrap_tags=False) - - for ca_name, ca_cert in ca_certs.items(): - if verify_crl(crl, ca_cert): - crl_ca_name = ca_name - break - - if crl and crl_ca_name: - crl_pem = encode_certificate(crl) - config.set(pki_base + ['ca', crl_ca_name, 'crl'], value=wrapped_pem_to_config_value(crl_pem)) - else: - print(f'Failed to migrate CRL on openvpn interface {interface}') - - config.delete(x509_base + ['crl-file']) - - if config.exists(x509_base + ['cert-file']): - if not config.exists(pki_base + ['certificate']): - config.set(pki_base + ['certificate']) - config.set_tag(pki_base + ['certificate']) - - cert_file = config.return_value(x509_base + ['cert-file']) - cert_path = os.path.join(AUTH_DIR, cert_file) - cert = None - - if os.path.isfile(cert_path): - if not os.access(cert_path, os.R_OK): - run(f'sudo chmod 644 {cert_path}') - - with open(cert_path, 'r') as f: - cert_data = f.read() - cert = load_certificate(cert_data, wrap_tags=False) - - if cert: - cert_pem = encode_certificate(cert) - config.set(pki_base + ['certificate', pki_name, 'certificate'], value=wrapped_pem_to_config_value(cert_pem)) - config.set(x509_base + ['certificate'], value=pki_name) - else: - print(f'Failed to migrate certificate on openvpn interface {interface}') - - config.delete(x509_base + ['cert-file']) - - if config.exists(x509_base + ['key-file']): - key_file = config.return_value(x509_base + ['key-file']) - key_path = os.path.join(AUTH_DIR, key_file) - key = None - - if os.path.isfile(key_path): - if not os.access(key_path, os.R_OK): - run(f'sudo chmod 644 {key_path}') - - with open(key_path, 'r') as f: - key_data = f.read() - key = load_private_key(key_data, passphrase=None, wrap_tags=False) - - if key: - key_pem = encode_private_key(key, passphrase=None) - config.set(pki_base + ['certificate', pki_name, 'private', 'key'], value=wrapped_pem_to_config_value(key_pem)) - else: - print(f'Failed to migrate private key on openvpn interface {interface}') - - config.delete(x509_base + ['key-file']) - - if config.exists(x509_base + ['dh-file']): - if not config.exists(pki_base + ['dh']): - config.set(pki_base + ['dh']) - config.set_tag(pki_base + ['dh']) - - dh_file = config.return_value(x509_base + ['dh-file']) - dh_path = os.path.join(AUTH_DIR, dh_file) - dh = None - - if os.path.isfile(dh_path): - if not os.access(dh_path, os.R_OK): - run(f'sudo chmod 644 {dh_path}') - - with open(dh_path, 'r') as f: - dh_data = f.read() - dh = load_dh_parameters(dh_data, wrap_tags=False) - - if dh: - dh_pem = encode_dh_parameters(dh) - config.set(pki_base + ['dh', pki_name, 'parameters'], value=wrapped_pem_to_config_value(dh_pem)) - config.set(x509_base + ['dh-params'], value=pki_name) - else: - print(f'Failed to migrate DH parameters on openvpn interface {interface}') - - config.delete(x509_base + ['dh-file']) - -# Wireguard -base = ['interfaces', 'wireguard'] - -if config.exists(base): - for interface in config.list_nodes(base): - private_key_path = base + [interface, 'private-key'] - - key_file = 'default' - if config.exists(private_key_path): - key_file = config.return_value(private_key_path) - - full_key_path = f'/config/auth/wireguard/{key_file}/private.key' + if config.exists(x509_base + ['key-file']): + key_file = config.return_value(x509_base + ['key-file']) + key_path = os.path.join(AUTH_DIR, key_file) + key = None + + if os.path.isfile(key_path): + if not os.access(key_path, os.R_OK): + run(f'sudo chmod 644 {key_path}') - if not os.path.exists(full_key_path): - print(f'Could not find wireguard private key for migration on interface "{interface}"') - continue - - with open(full_key_path, 'r') as f: - key_data = f.read().strip() - config.set(private_key_path, value=key_data) - - for peer in config.list_nodes(base + [interface, 'peer']): - config.rename(base + [interface, 'peer', peer, 'pubkey'], 'public-key') + with open(key_path, 'r') as f: + key_data = f.read() + key = load_private_key(key_data, passphrase=None, wrap_tags=False) -# Ethernet EAPoL -base = ['interfaces', 'ethernet'] - -if config.exists(base): - for interface in config.list_nodes(base): - if not config.exists(base + [interface, 'eapol']): - continue + if key: + key_pem = encode_private_key(key, passphrase=None) + config.set(pki_base + ['certificate', pki_name, 'private', 'key'], value=wrapped_pem_to_config_value(key_pem)) + else: + print(f'Failed to migrate private key on eapol config for interface {interface}') - x509_base = base + [interface, 'eapol'] - pki_name = f'eapol_{interface}' - - if config.exists(x509_base + ['ca-cert-file']): - if not config.exists(pki_base + ['ca']): - config.set(pki_base + ['ca']) - config.set_tag(pki_base + ['ca']) - - cert_file = config.return_value(x509_base + ['ca-cert-file']) - cert_path = os.path.join(AUTH_DIR, cert_file) - cert = None - - if os.path.isfile(cert_path): - if not os.access(cert_path, os.R_OK): - run(f'sudo chmod 644 {cert_path}') - - with open(cert_path, 'r') as f: - cert_data = f.read() - cert = load_certificate(cert_data, wrap_tags=False) - - if cert: - cert_pem = encode_certificate(cert) - config.set(pki_base + ['ca', pki_name, 'certificate'], value=wrapped_pem_to_config_value(cert_pem)) - config.set(x509_base + ['ca-certificate'], value=pki_name) - else: - print(f'Failed to migrate CA certificate on eapol config for interface {interface}') - - config.delete(x509_base + ['ca-cert-file']) - - if config.exists(x509_base + ['cert-file']): - if not config.exists(pki_base + ['certificate']): - config.set(pki_base + ['certificate']) - config.set_tag(pki_base + ['certificate']) - - cert_file = config.return_value(x509_base + ['cert-file']) - cert_path = os.path.join(AUTH_DIR, cert_file) - cert = None - - if os.path.isfile(cert_path): - if not os.access(cert_path, os.R_OK): - run(f'sudo chmod 644 {cert_path}') - - with open(cert_path, 'r') as f: - cert_data = f.read() - cert = load_certificate(cert_data, wrap_tags=False) - - if cert: - cert_pem = encode_certificate(cert) - config.set(pki_base + ['certificate', pki_name, 'certificate'], value=wrapped_pem_to_config_value(cert_pem)) - config.set(x509_base + ['certificate'], value=pki_name) - else: - print(f'Failed to migrate certificate on eapol config for interface {interface}') - - config.delete(x509_base + ['cert-file']) - - if config.exists(x509_base + ['key-file']): - key_file = config.return_value(x509_base + ['key-file']) - key_path = os.path.join(AUTH_DIR, key_file) - key = None - - if os.path.isfile(key_path): - if not os.access(key_path, os.R_OK): - run(f'sudo chmod 644 {key_path}') - - with open(key_path, 'r') as f: - key_data = f.read() - key = load_private_key(key_data, passphrase=None, wrap_tags=False) - - if key: - key_pem = encode_private_key(key, passphrase=None) - config.set(pki_base + ['certificate', pki_name, 'private', 'key'], value=wrapped_pem_to_config_value(key_pem)) - else: - print(f'Failed to migrate private key on eapol config for interface {interface}') - - config.delete(x509_base + ['key-file']) - -try: - with open(file_name, 'w') as f: - f.write(config.to_string()) -except OSError as e: - print("Failed to save the modified config: {}".format(e)) - sys.exit(1) + config.delete(x509_base + ['key-file']) diff --git a/src/migration-scripts/interfaces/26-to-27 b/src/migration-scripts/interfaces/26-to-27 old mode 100755 new mode 100644 index 429ab650f..3f58de02c --- a/src/migration-scripts/interfaces/26-to-27 +++ b/src/migration-scripts/interfaces/26-to-27 @@ -1,52 +1,35 @@ -#!/usr/bin/env python3 +# Copyright 2022-2024 VyOS maintainers and contributors <maintainers@vyos.io> # -# Copyright (C) 2022-2024 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 program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, +# 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 General Public License for more details. +# 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 General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. # T4384: pppoe: replace default-route CLI option with common CLI nodes already # present for DHCP -from sys import argv from vyos.configtree import ConfigTree -if len(argv) < 2: - print("Must specify file name!") - exit(1) - -file_name = argv[1] -with open(file_name, 'r') as f: - config_file = f.read() - -base = ['interfaces', 'pppoe'] -config = ConfigTree(config_file) - -if not config.exists(base): - exit(0) - -for ifname in config.list_nodes(base): - tmp_config = base + [ifname, 'default-route'] - if config.exists(tmp_config): - # Retrieve current config value - value = config.return_value(tmp_config) - # Delete old Config node - config.delete(tmp_config) - if value == 'none': - config.set(base + [ifname, 'no-default-route']) - -try: - with open(file_name, 'w') as f: - f.write(config.to_string()) -except OSError as e: - print(f'Failed to save the modified config: {e}') - exit(1) +def migrate(config: ConfigTree) -> None: + base = ['interfaces', 'pppoe'] + + if not config.exists(base): + return + + for ifname in config.list_nodes(base): + tmp_config = base + [ifname, 'default-route'] + if config.exists(tmp_config): + # Retrieve current config value + value = config.return_value(tmp_config) + # Delete old Config node + config.delete(tmp_config) + if value == 'none': + config.set(base + [ifname, 'no-default-route']) diff --git a/src/migration-scripts/interfaces/27-to-28 b/src/migration-scripts/interfaces/27-to-28 old mode 100755 new mode 100644 index 9f5e93b5f..eb9363e39 --- a/src/migration-scripts/interfaces/27-to-28 +++ b/src/migration-scripts/interfaces/27-to-28 @@ -1,48 +1,29 @@ -#!/usr/bin/env python3 +# Copyright 2023-2024 VyOS maintainers and contributors <maintainers@vyos.io> # -# Copyright (C) 2023-2024 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 program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, +# 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 General Public License for more details. +# 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 General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. # T4995: pppoe, wwan, sstpc-client rename "authentication user" CLI node # to "authentication username" -from sys import argv - from vyos.configtree import ConfigTree -if len(argv) < 2: - print("Must specify file name!") - exit(1) - -file_name = argv[1] -with open(file_name, 'r') as f: - config_file = f.read() - -config = ConfigTree(config_file) - -for type in ['pppoe', 'sstpc-client', 'wwam']: - base = ['interfaces', type] - if not config.exists(base): - continue - for interface in config.list_nodes(base): - auth_base = base + [interface, 'authentication', 'user'] - if config.exists(auth_base): - config.rename(auth_base, 'username') - -try: - with open(file_name, 'w') as f: - f.write(config.to_string()) -except OSError as e: - print(f'Failed to save the modified config: {e}') - exit(1) +def migrate(config: ConfigTree) -> None: + for type in ['pppoe', 'sstpc-client', 'wwam']: + base = ['interfaces', type] + if not config.exists(base): + continue + for interface in config.list_nodes(base): + auth_base = base + [interface, 'authentication', 'user'] + if config.exists(auth_base): + config.rename(auth_base, 'username') diff --git a/src/migration-scripts/interfaces/28-to-29 b/src/migration-scripts/interfaces/28-to-29 old mode 100755 new mode 100644 index 0437977dc..886d49e2c --- a/src/migration-scripts/interfaces/28-to-29 +++ b/src/migration-scripts/interfaces/28-to-29 @@ -1,52 +1,35 @@ -#!/usr/bin/env python3 +# Copyright 2023-2024 VyOS maintainers and contributors <maintainers@vyos.io> # -# Copyright (C) 2023-2024 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 program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, +# 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 General Public License for more details. +# 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 General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. # T5034: tunnel: rename "multicast enable" CLI node to "enable-multicast" # valueless node. -from sys import argv from vyos.configtree import ConfigTree -if len(argv) < 2: - print("Must specify file name!") - exit(1) - -file_name = argv[1] -with open(file_name, 'r') as f: - config_file = f.read() - base = ['interfaces', 'tunnel'] -config = ConfigTree(config_file) - -if not config.exists(base): - exit(0) - -for ifname in config.list_nodes(base): - multicast_base = base + [ifname, 'multicast'] - if config.exists(multicast_base): - tmp = config.return_value(multicast_base) - print(tmp) - # Delete old Config node - config.delete(multicast_base) - if tmp == 'enable': - config.set(base + [ifname, 'enable-multicast']) -try: - with open(file_name, 'w') as f: - f.write(config.to_string()) -except OSError as e: - print(f'Failed to save the modified config: {e}') - exit(1) +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + return + + for ifname in config.list_nodes(base): + multicast_base = base + [ifname, 'multicast'] + if config.exists(multicast_base): + tmp = config.return_value(multicast_base) + print(tmp) + # Delete old Config node + config.delete(multicast_base) + if tmp == 'enable': + config.set(base + [ifname, 'enable-multicast']) diff --git a/src/migration-scripts/interfaces/29-to-30 b/src/migration-scripts/interfaces/29-to-30 old mode 100755 new mode 100644 index 80aad1d44..7b32d871e --- a/src/migration-scripts/interfaces/29-to-30 +++ b/src/migration-scripts/interfaces/29-to-30 @@ -1,47 +1,30 @@ -#!/usr/bin/env python3 +# Copyright 2023-2024 VyOS maintainers and contributors <maintainers@vyos.io> # -# Copyright (C) 2023-2024 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 program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, +# 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 General Public License for more details. +# 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 General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. # T5286: remove XDP support in favour of VPP -from sys import argv from vyos.configtree import ConfigTree -if len(argv) < 2: - print("Must specify file name!") - exit(1) - -file_name = argv[1] -with open(file_name, 'r') as f: - config_file = f.read() - supports_xdp = ['bonding', 'ethernet'] -config = ConfigTree(config_file) - -for if_type in supports_xdp: - base = ['interfaces', if_type] - if not config.exists(base): - continue - for interface in config.list_nodes(base): - if_base = base + [interface] - if config.exists(if_base + ['xdp']): - config.delete(if_base + ['xdp']) -try: - with open(file_name, 'w') as f: - f.write(config.to_string()) -except OSError as e: - print(f'Failed to save the modified config: {e}') - exit(1) +def migrate(config: ConfigTree) -> None: + for if_type in supports_xdp: + base = ['interfaces', if_type] + if not config.exists(base): + continue + for interface in config.list_nodes(base): + if_base = base + [interface] + if config.exists(if_base + ['xdp']): + config.delete(if_base + ['xdp']) diff --git a/src/migration-scripts/interfaces/3-to-4 b/src/migration-scripts/interfaces/3-to-4 old mode 100755 new mode 100644 index c7fd7d01d..4e56200e1 --- a/src/migration-scripts/interfaces/3-to-4 +++ b/src/migration-scripts/interfaces/3-to-4 @@ -1,97 +1,93 @@ -#!/usr/bin/env python3 +# Copyright 2019-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# 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 <http://www.gnu.org/licenses/>. # Change syntax of wireless interfaces # Migrate boolean nodes to valueless -import sys from vyos.configtree import ConfigTree -if len(sys.argv) < 2: - print("Must specify file name!") - sys.exit(1) - -file_name = sys.argv[1] - -with open(file_name, 'r') as f: - config_file = f.read() - -config = ConfigTree(config_file) base = ['interfaces', 'wireless'] -if not config.exists(base): - # Nothing to do - sys.exit(0) -else: +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + # Nothing to do + return + for wifi in config.list_nodes(base): # as converting a node to bool is always the same, we can script it to_bool_nodes = ['capabilities ht 40MHz-incapable', 'capabilities ht auto-powersave', 'capabilities ht delayed-block-ack', 'capabilities ht dsss-cck-40', 'capabilities ht greenfield', 'capabilities ht ldpc', 'capabilities ht lsig-protection', 'capabilities ht stbc tx', 'capabilities require-ht', 'capabilities require-vht', 'capabilities vht antenna-pattern-fixed', 'capabilities vht ldpc', 'capabilities vht stbc tx', 'capabilities vht tx-powersave', 'capabilities vht vht-cf', 'expunge-failing-stations', 'isolate-stations'] for node in to_bool_nodes: if config.exists(base + [wifi, node]): tmp = config.return_value(base + [wifi, node]) # delete old node config.delete(base + [wifi, node]) # set new node if it was enabled if tmp == 'true': # OLD CLI used camel casing in 40MHz-incapable which is # not supported in the new backend. Convert all to lower-case config.set(base + [wifi, node.lower()]) # Remove debug node if config.exists(base + [wifi, 'debug']): config.delete(base + [wifi, 'debug']) # RADIUS servers if config.exists(base + [wifi, 'security', 'wpa', 'radius-server']): for server in config.list_nodes(base + [wifi, 'security', 'wpa', 'radius-server']): base_server = base + [wifi, 'security', 'wpa', 'radius-server', server] # Migrate RADIUS shared secret if config.exists(base_server + ['secret']): key = config.return_value(base_server + ['secret']) # write new configuration node config.set(base + [wifi, 'security', 'wpa', 'radius', 'server', server, 'key'], value=key) # format as tag node config.set_tag(base + [wifi, 'security', 'wpa', 'radius', 'server']) # Migrate RADIUS port if config.exists(base_server + ['port']): port = config.return_value(base_server + ['port']) # write new configuration node config.set(base + [wifi, 'security', 'wpa', 'radius', 'server', server, 'port'], value=port) # format as tag node config.set_tag(base + [wifi, 'security', 'wpa', 'radius', 'server']) # Migrate RADIUS accounting if config.exists(base_server + ['accounting']): port = config.return_value(base_server + ['accounting']) # write new configuration node config.set(base + [wifi, 'security', 'wpa', 'radius', 'server', server, 'accounting']) # format as tag node config.set_tag(base + [wifi, 'security', 'wpa', 'radius', 'server']) # delete old radius-server nodes config.delete(base + [wifi, 'security', 'wpa', 'radius-server']) - - try: - with open(file_name, 'w') as f: - f.write(config.to_string()) - except OSError as e: - print("Failed to save the modified config: {}".format(e)) - sys.exit(1) diff --git a/src/migration-scripts/interfaces/30-to-31 b/src/migration-scripts/interfaces/30-to-31 old mode 100755 new mode 100644 index 894106ef4..7e509dd86 --- a/src/migration-scripts/interfaces/30-to-31 +++ b/src/migration-scripts/interfaces/30-to-31 @@ -1,71 +1,56 @@ #!/usr/bin/env python3 +# Copyright 2023-2024 VyOS maintainers and contributors <maintainers@vyos.io> # -# Copyright (C) 2021-2023 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 program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, +# 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 General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. # -# Deletes Wireguard peers if they have the same public key as the router has. +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# T5254: Fixed changing ethernet when it is a bond member import json -from sys import argv -from sys import exit from vyos.configtree import ConfigTree from vyos.ifconfig import EthernetIf from vyos.ifconfig import BondIf from vyos.utils.dict import dict_to_paths_values -if len(argv) < 2: - print("Must specify file name!") - exit(1) +base = ['interfaces', 'bonding'] -file_name = argv[1] -with open(file_name, 'r') as f: - config_file = f.read() - base = ['interfaces', 'bonding'] +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + # Nothing to do + return -config = ConfigTree(config_file) -if not config.exists(base): - # Nothing to do - exit(0) -for bond in config.list_nodes(base): - member_base = base + [bond, 'member', 'interface'] - if config.exists(member_base): - for interface in config.return_values(member_base): - if_base = ['interfaces', 'ethernet', interface] - if config.exists(if_base): - config_ethernet = json.loads(config.get_subtree(if_base).to_json()) - eth_dict_paths = dict_to_paths_values(config_ethernet) - for option_path, option_value in eth_dict_paths.items(): - # If option is allowed for changing then continue - converted_path = option_path.replace('-','_') - if converted_path in EthernetIf.get_bond_member_allowed_options(): - continue - # if option is inherited from bond then continue - if converted_path in BondIf.get_inherit_bond_options(): - continue - option_path_list = option_path.split('.') - config.delete(if_base + option_path_list) - del option_path_list[-1] - # delete empty node from config - while len(option_path_list) > 0: - if config.list_nodes(if_base + option_path_list): - break + for bond in config.list_nodes(base): + member_base = base + [bond, 'member', 'interface'] + if config.exists(member_base): + for interface in config.return_values(member_base): + if_base = ['interfaces', 'ethernet', interface] + if config.exists(if_base): + config_ethernet = json.loads(config.get_subtree(if_base).to_json()) + eth_dict_paths = dict_to_paths_values(config_ethernet) + for option_path, option_value in eth_dict_paths.items(): + # If option is allowed for changing then continue + converted_path = option_path.replace('-','_') + if converted_path in EthernetIf.get_bond_member_allowed_options(): + continue + # if option is inherited from bond then continue + if converted_path in BondIf.get_inherit_bond_options(): + continue + option_path_list = option_path.split('.') config.delete(if_base + option_path_list) del option_path_list[-1] - -try: - with open(file_name, 'w') as f: - f.write(config.to_string()) -except OSError as e: - print(f'Failed to save the modified config: {e}') - exit(1) + # delete empty node from config + while len(option_path_list) > 0: + if config.list_nodes(if_base + option_path_list): + break + config.delete(if_base + option_path_list) + del option_path_list[-1] diff --git a/src/migration-scripts/interfaces/31-to-32 b/src/migration-scripts/interfaces/31-to-32 old mode 100755 new mode 100644 index 0fc27b70a..24077ed24 --- a/src/migration-scripts/interfaces/31-to-32 +++ b/src/migration-scripts/interfaces/31-to-32 @@ -1,55 +1,37 @@ -#!/usr/bin/env python3 +# Copyright 2023-2024 VyOS maintainers and contributors <maintainers@vyos.io> # -# Copyright (C) 2023 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 program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, +# 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 General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. +# 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 <http://www.gnu.org/licenses/>. + # T5671: change port to IANA assigned default port # T5759: change default MTU 1450 -> 1500 -from sys import argv -from sys import exit from vyos.configtree import ConfigTree -if len(argv) < 2: - print("Must specify file name!") - exit(1) - -file_name = argv[1] -with open(file_name, 'r') as f: - config_file = f.read() - base = ['interfaces', 'vxlan'] -config = ConfigTree(config_file) -if not config.exists(base): - # Nothing to do - exit(0) - -for vxlan in config.list_nodes(base): - if config.exists(base + [vxlan, 'external']): - config.delete(base + [vxlan, 'external']) - config.set(base + [vxlan, 'parameters', 'external']) +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + # Nothing to do + return - if not config.exists(base + [vxlan, 'port']): - config.set(base + [vxlan, 'port'], value='8472') + for vxlan in config.list_nodes(base): + if config.exists(base + [vxlan, 'external']): + config.delete(base + [vxlan, 'external']) + config.set(base + [vxlan, 'parameters', 'external']) - if not config.exists(base + [vxlan, 'mtu']): - config.set(base + [vxlan, 'mtu'], value='1450') + if not config.exists(base + [vxlan, 'port']): + config.set(base + [vxlan, 'port'], value='8472') -try: - with open(file_name, 'w') as f: - f.write(config.to_string()) -except OSError as e: - print(f'Failed to save the modified config: {e}') - exit(1) + if not config.exists(base + [vxlan, 'mtu']): + config.set(base + [vxlan, 'mtu'], value='1450') diff --git a/src/migration-scripts/interfaces/32-to-33 b/src/migration-scripts/interfaces/32-to-33 old mode 100755 new mode 100644 index caf588474..c7b1c5b36 --- a/src/migration-scripts/interfaces/32-to-33 +++ b/src/migration-scripts/interfaces/32-to-33 @@ -1,57 +1,40 @@ #!/usr/bin/env python3 # # Copyright (C) 2024 VyOS maintainers and contributors # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License version 2 or later as # published by the Free Software Foundation. # # This program 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 General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. # # T6318: WiFi country-code should be set system-wide instead of per-device -from sys import argv -from sys import exit from vyos.configtree import ConfigTree -if len(argv) < 2: - print("Must specify file name!") - exit(1) - -file_name = argv[1] -with open(file_name, 'r') as f: - config_file = f.read() - base = ['interfaces', 'wireless'] -config = ConfigTree(config_file) -if not config.exists(base): - # Nothing to do - exit(0) - -installed = False -for interface in config.list_nodes(base): - cc_path = base + [interface, 'country-code'] - if config.exists(cc_path): - tmp = config.return_value(cc_path) - config.delete(cc_path) - - # There can be only ONE wireless country-code per device, everything - # else makes no sense as a WIFI router can not operate in two - # different countries - if not installed: - config.set(['system', 'wireless', 'country-code'], value=tmp) - installed = True - -try: - with open(file_name, 'w') as f: - f.write(config.to_string()) -except OSError as e: - print(f'Failed to save the modified config: {e}') - exit(1) +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + # Nothing to do + return + + installed = False + for interface in config.list_nodes(base): + cc_path = base + [interface, 'country-code'] + if config.exists(cc_path): + tmp = config.return_value(cc_path) + config.delete(cc_path) + + # There can be only ONE wireless country-code per device, everything + # else makes no sense as a WIFI router can not operate in two + # different countries + if not installed: + config.set(['system', 'wireless', 'country-code'], value=tmp) + installed = True diff --git a/src/migration-scripts/interfaces/4-to-5 b/src/migration-scripts/interfaces/4-to-5 old mode 100755 new mode 100644 index 68d81e846..93fa7c393 --- a/src/migration-scripts/interfaces/4-to-5 +++ b/src/migration-scripts/interfaces/4-to-5 @@ -1,112 +1,106 @@ -#!/usr/bin/env python3 +# Copyright 2019-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# 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 <http://www.gnu.org/licenses/>. # De-nest PPPoE interfaces # Migrate boolean nodes to valueless -import sys from vyos.configtree import ConfigTree def migrate_dialer(config, tree, intf): for pppoe in config.list_nodes(tree): # assemble string, 0 -> pppoe0 new_base = ['interfaces', 'pppoe'] pppoe_base = new_base + ['pppoe' + pppoe] config.set(new_base) # format as tag node to avoid loading problems config.set_tag(new_base) # Copy the entire old node to the new one before migrating individual # parts config.copy(tree + [pppoe], pppoe_base) # Instead of letting the user choose between auto and none # where auto is default, it makes more sesne to just offer # an option to disable the default behavior (declutter CLI) if config.exists(pppoe_base + ['name-server']): tmp = config.return_value(pppoe_base + ['name-server']) if tmp == "none": config.set(pppoe_base + ['no-peer-dns']) config.delete(pppoe_base + ['name-server']) # Migrate user-id and password nodes under an 'authentication' # node if config.exists(pppoe_base + ['user-id']): user = config.return_value(pppoe_base + ['user-id']) config.set(pppoe_base + ['authentication', 'user'], value=user) config.delete(pppoe_base + ['user-id']) if config.exists(pppoe_base + ['password']): pwd = config.return_value(pppoe_base + ['password']) config.set(pppoe_base + ['authentication', 'password'], value=pwd) config.delete(pppoe_base + ['password']) # remove enable-ipv6 node and rather place it under ipv6 node if config.exists(pppoe_base + ['enable-ipv6']): config.set(pppoe_base + ['ipv6', 'enable']) config.delete(pppoe_base + ['enable-ipv6']) # Source interface migration config.set(pppoe_base + ['source-interface'], value=intf) # Remove IPv6 router-advert nodes as this makes no sense on a # client diale rinterface to send RAs back into the network # https://vyos.dev/T2055 ipv6_ra = pppoe_base + ['ipv6', 'router-advert'] if config.exists(ipv6_ra): config.delete(ipv6_ra) - -if __name__ == '__main__': - if len(sys.argv) < 2: - print("Must specify file name!") - exit(1) - - file_name = sys.argv[1] - - with open(file_name, 'r') as f: - config_file = f.read() - - config = ConfigTree(config_file) +def migrate(config: ConfigTree) -> None: pppoe_links = ['bonding', 'ethernet'] for link_type in pppoe_links: if not config.exists(['interfaces', link_type]): continue for interface in config.list_nodes(['interfaces', link_type]): # check if PPPoE exists base_if = ['interfaces', link_type, interface] pppoe_if = base_if + ['pppoe'] if config.exists(pppoe_if): for dialer in config.list_nodes(pppoe_if): migrate_dialer(config, pppoe_if, interface) # Delete old PPPoE interface config.delete(pppoe_if) # bail out early if there are no VLAN interfaces to migrate if not config.exists(base_if + ['vif']): continue # Migrate PPPoE interfaces attached to a VLAN for vlan in config.list_nodes(base_if + ['vif']): vlan_if = base_if + ['vif', vlan] pppoe_if = vlan_if + ['pppoe'] if config.exists(pppoe_if): for dialer in config.list_nodes(pppoe_if): intf = "{}.{}".format(interface, vlan) migrate_dialer(config, pppoe_if, intf) # Delete old PPPoE interface config.delete(pppoe_if) # Add interface description that this is required for PPPoE if not config.exists(vlan_if + ['description']): config.set(vlan_if + ['description'], value='PPPoE link interface') - - try: - with open(file_name, 'w') as f: - f.write(config.to_string()) - except OSError as e: - print("Failed to save the modified config: {}".format(e)) - sys.exit(1) diff --git a/src/migration-scripts/interfaces/5-to-6 b/src/migration-scripts/interfaces/5-to-6 old mode 100755 new mode 100644 index 9d9a49c2d..44c32ba63 --- a/src/migration-scripts/interfaces/5-to-6 +++ b/src/migration-scripts/interfaces/5-to-6 @@ -1,133 +1,114 @@ -#!/usr/bin/env python3 +# Copyright 202-2024 VyOS maintainers and contributors <maintainers@vyos.io> # -# Copyright (C) 2020-2021 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 program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, +# 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 General Public License for more details. +# 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 General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. # Migrate IPv6 router advertisments from a nested interface configuration to # a denested "service router-advert" -import sys from vyos.configtree import ConfigTree def copy_rtradv(c, old_base, interface): base = ['service', 'router-advert', 'interface'] if c.exists(old_base): if not c.exists(base): c.set(base) c.set_tag(base) # take the old node as a whole and copy it to new new path, # additional migrations will be done afterwards new_base = base + [interface] c.copy(old_base, new_base) c.delete(old_base) # cur-hop-limit has been renamed to hop-limit if c.exists(new_base + ['cur-hop-limit']): c.rename(new_base + ['cur-hop-limit'], 'hop-limit') bool_cleanup = ['managed-flag', 'other-config-flag'] for bool in bool_cleanup: if c.exists(new_base + [bool]): tmp = c.return_value(new_base + [bool]) c.delete(new_base + [bool]) if tmp == 'true': c.set(new_base + [bool]) # max/min interval moved to subnode intervals = ['max-interval', 'min-interval'] for interval in intervals: if c.exists(new_base + [interval]): tmp = c.return_value(new_base + [interval]) c.delete(new_base + [interval]) min_max = interval.split('-')[0] c.set(new_base + ['interval', min_max], value=tmp) # cleanup boolean nodes in individual route route_base = new_base + ['route'] if c.exists(route_base): for route in config.list_nodes(route_base): if c.exists(route_base + [route, 'remove-route']): tmp = c.return_value(route_base + [route, 'remove-route']) c.delete(route_base + [route, 'remove-route']) if tmp == 'false': c.set(route_base + [route, 'no-remove-route']) # cleanup boolean nodes in individual prefix prefix_base = new_base + ['prefix'] if c.exists(prefix_base): for prefix in config.list_nodes(prefix_base): if c.exists(prefix_base + [prefix, 'autonomous-flag']): tmp = c.return_value(prefix_base + [prefix, 'autonomous-flag']) c.delete(prefix_base + [prefix, 'autonomous-flag']) if tmp == 'false': c.set(prefix_base + [prefix, 'no-autonomous-flag']) if c.exists(prefix_base + [prefix, 'on-link-flag']): tmp = c.return_value(prefix_base + [prefix, 'on-link-flag']) c.delete(prefix_base + [prefix, 'on-link-flag']) if tmp == 'true': c.set(prefix_base + [prefix, 'on-link-flag']) # router advertisement can be individually disabled per interface # the node has been renamed from send-advert {true | false} to no-send-advert if c.exists(new_base + ['send-advert']): tmp = c.return_value(new_base + ['send-advert']) c.delete(new_base + ['send-advert']) if tmp == 'false': c.set(new_base + ['no-send-advert']) # link-mtu advertisement was formerly disabled by setting its value to 0 # ... this makes less sense - if it should not be send, just do not # configure it if c.exists(new_base + ['link-mtu']): tmp = c.return_value(new_base + ['link-mtu']) if tmp == '0': c.delete(new_base + ['link-mtu']) -if __name__ == '__main__': - if len(sys.argv) < 2: - print("Must specify file name!") - exit(1) - - file_name = sys.argv[1] - with open(file_name, 'r') as f: - config_file = f.read() - - config = ConfigTree(config_file) - +def migrate(config: ConfigTree) -> None: # list all individual interface types like dummy, ethernet and so on for if_type in config.list_nodes(['interfaces']): base_if_type = ['interfaces', if_type] # for every individual interface we need to check if there is an # ipv6 ra configured ... and also for every VIF (VLAN) interface for intf in config.list_nodes(base_if_type): old_base = base_if_type + [intf, 'ipv6', 'router-advert'] copy_rtradv(config, old_base, intf) vif_base = base_if_type + [intf, 'vif'] if config.exists(vif_base): for vif in config.list_nodes(vif_base): old_base = vif_base + [vif, 'ipv6', 'router-advert'] vlan_name = f'{intf}.{vif}' copy_rtradv(config, old_base, vlan_name) - - try: - with open(file_name, 'w') as f: - f.write(config.to_string()) - except OSError as e: - print("Failed to save the modified config: {}".format(e)) - sys.exit(1) diff --git a/src/migration-scripts/interfaces/6-to-7 b/src/migration-scripts/interfaces/6-to-7 old mode 100755 new mode 100644 index 49b853d90..e60121eec --- a/src/migration-scripts/interfaces/6-to-7 +++ b/src/migration-scripts/interfaces/6-to-7 @@ -1,63 +1,45 @@ -#!/usr/bin/env python3 +# Copyright 2020-2024 VyOS maintainers and contributors <maintainers@vyos.io> # -# Copyright (C) 2020 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 program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, +# 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 General Public License for more details. +# 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 General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. # Remove network provider name from CLI and rather use provider APN from CLI -import sys from vyos.configtree import ConfigTree -if __name__ == '__main__': - if len(sys.argv) < 2: - print("Must specify file name!") - exit(1) - - file_name = sys.argv[1] - with open(file_name, 'r') as f: - config_file = f.read() - - config = ConfigTree(config_file) +def migrate(config: ConfigTree) -> None: base = ['interfaces', 'wirelessmodem'] if not config.exists(base): # Nothing to do - sys.exit(0) + return # list all individual wwan/wireless modem interfaces for i in config.list_nodes(base): iface = base + [i] # only three carries have been supported in the past, thus # this will be fairly simple \o/ - and only one (AT&T) did # configure an APN if config.exists(iface + ['network']): network = config.return_value(iface + ['network']) if network == "att": apn = 'isp.cingular' config.set(iface + ['apn'], value=apn) config.delete(iface + ['network']) # synchronize DNS configuration with PPPoE interfaces to have a # uniform CLI experience if config.exists(iface + ['no-dns']): config.rename(iface + ['no-dns'], 'no-peer-dns') - - try: - with open(file_name, 'w') as f: - f.write(config.to_string()) - except OSError as e: - print("Failed to save the modified config: {}".format(e)) - sys.exit(1) diff --git a/src/migration-scripts/interfaces/7-to-8 b/src/migration-scripts/interfaces/7-to-8 old mode 100755 new mode 100644 index 9343a48a8..43ae320ab --- a/src/migration-scripts/interfaces/7-to-8 +++ b/src/migration-scripts/interfaces/7-to-8 @@ -1,77 +1,59 @@ -#!/usr/bin/env python3 +# Copyright 2020-2024 VyOS maintainers and contributors <maintainers@vyos.io> # -# Copyright (C) 2020-2023 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 program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, +# 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 General Public License for more details. +# 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 General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. # Split WireGuard endpoint into address / port nodes to make use of common # validators import os -from sys import exit, argv from vyos.configtree import ConfigTree from vyos.utils.permission import chown from vyos.utils.permission import chmod_750 def migrate_default_keys(): kdir = r'/config/auth/wireguard' if os.path.exists(f'{kdir}/private.key') and not os.path.exists(f'{kdir}/default/private.key'): location = f'{kdir}/default' if not os.path.exists(location): os.makedirs(location) chown(location, 'root', 'vyattacfg') chmod_750(location) os.rename(f'{kdir}/private.key', f'{location}/private.key') os.rename(f'{kdir}/public.key', f'{location}/public.key') -if __name__ == '__main__': - if len(argv) < 2: - print("Must specify file name!") - exit(1) - - file_name = argv[1] - with open(file_name, 'r') as f: - config_file = f.read() - - config = ConfigTree(config_file) +def migrate(config: ConfigTree) -> None: base = ['interfaces', 'wireguard'] migrate_default_keys() if not config.exists(base): # Nothing to do - exit(0) + return # list all individual wireguard interface isntance for i in config.list_nodes(base): iface = base + [i] for peer in config.list_nodes(iface + ['peer']): base_peer = iface + ['peer', peer] if config.exists(base_peer + ['endpoint']): endpoint = config.return_value(base_peer + ['endpoint']) address = endpoint.split(':')[0] port = endpoint.split(':')[1] # delete old node config.delete(base_peer + ['endpoint']) # setup new nodes config.set(base_peer + ['address'], value=address) config.set(base_peer + ['port'], value=port) - - try: - with open(file_name, 'w') as f: - f.write(config.to_string()) - except OSError as e: - print("Failed to save the modified config: {}".format(e)) - exit(1) diff --git a/src/migration-scripts/interfaces/8-to-9 b/src/migration-scripts/interfaces/8-to-9 old mode 100755 new mode 100644 index 960962be7..bae1b34fa --- a/src/migration-scripts/interfaces/8-to-9 +++ b/src/migration-scripts/interfaces/8-to-9 @@ -1,52 +1,33 @@ -#!/usr/bin/env python3 +# Copyright 2020-2024 VyOS maintainers and contributors <maintainers@vyos.io> # -# Copyright (C) 2020 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 program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, +# 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 General Public License for more details. +# 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 General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. # Rename link nodes to source-interface for the following interface types: # - vxlan # - pseudo-ethernet -from sys import exit, argv from vyos.configtree import ConfigTree -if __name__ == '__main__': - if len(argv) < 2: - print("Must specify file name!") - exit(1) - - file_name = argv[1] - with open(file_name, 'r') as f: - config_file = f.read() - - config = ConfigTree(config_file) - +def migrate(config: ConfigTree) -> None: for if_type in ['vxlan', 'pseudo-ethernet']: base = ['interfaces', if_type] if not config.exists(base): # Nothing to do continue # list all individual interface isntance for i in config.list_nodes(base): iface = base + [i] if config.exists(iface + ['link']): config.rename(iface + ['link'], 'source-interface') - - try: - with open(file_name, 'w') as f: - f.write(config.to_string()) - except OSError as e: - print("Failed to save the modified config: {}".format(e)) - exit(1) diff --git a/src/migration-scripts/interfaces/9-to-10 b/src/migration-scripts/interfaces/9-to-10 old mode 100755 new mode 100644 index e9b8cb784..cdfd7d432 --- a/src/migration-scripts/interfaces/9-to-10 +++ b/src/migration-scripts/interfaces/9-to-10 @@ -1,64 +1,45 @@ -#!/usr/bin/env python3 +# Copyright 2020-2024 VyOS maintainers and contributors <maintainers@vyos.io> # -# Copyright (C) 2020 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 program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, +# 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 General Public License for more details. +# 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 General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. # - rename CLI node 'dhcpv6-options delgate' to 'dhcpv6-options prefix-delegation # interface' # - rename CLI node 'interface-id' for prefix-delegation to 'address' as it # represents the local interface IPv6 address assigned by DHCPv6-PD -from sys import exit, argv from vyos.configtree import ConfigTree -if __name__ == '__main__': - if len(argv) < 2: - print("Must specify file name!") - exit(1) - - file_name = argv[1] - with open(file_name, 'r') as f: - config_file = f.read() - - config = ConfigTree(config_file) - +def migrate(config: ConfigTree) -> None: for intf_type in config.list_nodes(['interfaces']): for intf in config.list_nodes(['interfaces', intf_type]): # cache current config tree base_path = ['interfaces', intf_type, intf, 'dhcpv6-options', 'delegate'] if config.exists(base_path): # cache new config tree new_path = ['interfaces', intf_type, intf, 'dhcpv6-options', 'prefix-delegation'] if not config.exists(new_path): config.set(new_path) # copy to new node config.copy(base_path, new_path + ['interface']) # rename interface-id to address for interface in config.list_nodes(new_path + ['interface']): config.rename(new_path + ['interface', interface, 'interface-id'], 'address') # delete old noe config.delete(base_path) - - try: - with open(file_name, 'w') as f: - f.write(config.to_string()) - except OSError as e: - print("Failed to save the modified config: {}".format(e)) - exit(1) diff --git a/src/migration-scripts/ipoe-server/1-to-2 b/src/migration-scripts/ipoe-server/1-to-2 old mode 100755 new mode 100644 index 6a7111541..034eacb10 --- a/src/migration-scripts/ipoe-server/1-to-2 +++ b/src/migration-scripts/ipoe-server/1-to-2 @@ -1,114 +1,94 @@ -#!/usr/bin/env python3 +# Copyright 2023-2024 VyOS maintainers and contributors <maintainers@vyos.io> # -# Copyright (C) 2023-2024 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 program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, +# 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 General Public License for more details. +# 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 General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. # - T4703: merge vlan-id and vlan-range to vlan CLI node # L2|L3 -> l2|l3 # mac-address -> mac # network-mode -> mode # - changed cli of all named pools # - moved gateway-address from pool to global configuration with / netmask # gateway can exist without pool if radius is used # and Framed-ip-address is transmited # - There are several gateway-addresses in ipoe # - default-pool by migration. # 1. The first pool that contains next-poll. # 2. Else, the first pool in the list -from sys import argv -from sys import exit from vyos.configtree import ConfigTree - -if len(argv) < 2: - print("Must specify file name!") - exit(1) - -file_name = argv[1] - -with open(file_name, 'r') as f: - config_file = f.read() - -config = ConfigTree(config_file) base = ['service', 'ipoe-server'] -if not config.exists(base): - exit(0) - -if config.exists(base + ['authentication', 'interface']): - for interface in config.list_nodes(base + ['authentication', 'interface']): - config.rename(base + ['authentication', 'interface', interface, 'mac-address'], 'mac') - - mac_base = base + ['authentication', 'interface', interface, 'mac'] - for mac in config.list_nodes(mac_base): - vlan_config = mac_base + [mac, 'vlan-id'] - if config.exists(vlan_config): - config.rename(vlan_config, 'vlan') - -for interface in config.list_nodes(base + ['interface']): - base_path = base + ['interface', interface] - for vlan in ['vlan-id', 'vlan-range']: - if config.exists(base_path + [vlan]): - for tmp in config.return_values(base_path + [vlan]): - config.set(base_path + ['vlan'], value=tmp, replace=False) - config.delete(base_path + [vlan]) - - if config.exists(base_path + ['network-mode']): - tmp = config.return_value(base_path + ['network-mode']) - config.delete(base_path + ['network-mode']) - # Change L2|L3 to lower case l2|l3 - config.set(base_path + ['mode'], value=tmp.lower()) - -pool_base = base + ['client-ip-pool'] -if config.exists(pool_base): - default_pool = '' - gateway = '' - - #named pool migration - namedpools_base = pool_base + ['name'] - - for pool_name in config.list_nodes(namedpools_base): - pool_path = namedpools_base + [pool_name] - if config.exists(pool_path + ['subnet']): - subnet = config.return_value(pool_path + ['subnet']) - config.set(pool_base + [pool_name, 'range'], value=subnet, replace=False) - # Get netmask from subnet - mask = subnet.split("/")[1] - if config.exists(pool_path + ['next-pool']): - next_pool = config.return_value(pool_path + ['next-pool']) - config.set(pool_base + [pool_name, 'next-pool'], value=next_pool) - if not default_pool: - default_pool = pool_name - if config.exists(pool_path + ['gateway-address']) and mask: - gateway = f'{config.return_value(pool_path + ["gateway-address"])}/{mask}' - config.set(base + ['gateway-address'], value=gateway, replace=False) - - if not default_pool and config.list_nodes(namedpools_base): - default_pool = config.list_nodes(namedpools_base)[0] - - config.delete(namedpools_base) - - if default_pool: - config.set(base + ['default-pool'], value=default_pool) - # format as tag node - config.set_tag(pool_base) - -try: - with open(file_name, 'w') as f: - f.write(config.to_string()) -except OSError as e: - print("Failed to save the modified config: {}".format(e)) - exit(1) +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + return + + if config.exists(base + ['authentication', 'interface']): + for interface in config.list_nodes(base + ['authentication', 'interface']): + config.rename(base + ['authentication', 'interface', interface, 'mac-address'], 'mac') + + mac_base = base + ['authentication', 'interface', interface, 'mac'] + for mac in config.list_nodes(mac_base): + vlan_config = mac_base + [mac, 'vlan-id'] + if config.exists(vlan_config): + config.rename(vlan_config, 'vlan') + + for interface in config.list_nodes(base + ['interface']): + base_path = base + ['interface', interface] + for vlan in ['vlan-id', 'vlan-range']: + if config.exists(base_path + [vlan]): + for tmp in config.return_values(base_path + [vlan]): + config.set(base_path + ['vlan'], value=tmp, replace=False) + config.delete(base_path + [vlan]) + + if config.exists(base_path + ['network-mode']): + tmp = config.return_value(base_path + ['network-mode']) + config.delete(base_path + ['network-mode']) + # Change L2|L3 to lower case l2|l3 + config.set(base_path + ['mode'], value=tmp.lower()) + + pool_base = base + ['client-ip-pool'] + if config.exists(pool_base): + default_pool = '' + gateway = '' + + #named pool migration + namedpools_base = pool_base + ['name'] + + for pool_name in config.list_nodes(namedpools_base): + pool_path = namedpools_base + [pool_name] + if config.exists(pool_path + ['subnet']): + subnet = config.return_value(pool_path + ['subnet']) + config.set(pool_base + [pool_name, 'range'], value=subnet, replace=False) + # Get netmask from subnet + mask = subnet.split("/")[1] + if config.exists(pool_path + ['next-pool']): + next_pool = config.return_value(pool_path + ['next-pool']) + config.set(pool_base + [pool_name, 'next-pool'], value=next_pool) + if not default_pool: + default_pool = pool_name + if config.exists(pool_path + ['gateway-address']) and mask: + gateway = f'{config.return_value(pool_path + ["gateway-address"])}/{mask}' + config.set(base + ['gateway-address'], value=gateway, replace=False) + + if not default_pool and config.list_nodes(namedpools_base): + default_pool = config.list_nodes(namedpools_base)[0] + + config.delete(namedpools_base) + + if default_pool: + config.set(base + ['default-pool'], value=default_pool) + # format as tag node + config.set_tag(pool_base) diff --git a/src/migration-scripts/ipoe-server/2-to-3 b/src/migration-scripts/ipoe-server/2-to-3 old mode 100755 new mode 100644 index 0909315a8..dcd15e595 --- a/src/migration-scripts/ipoe-server/2-to-3 +++ b/src/migration-scripts/ipoe-server/2-to-3 @@ -1,58 +1,40 @@ -#!/usr/bin/env python3 +# Copyright 2024 VyOS maintainers and contributors <maintainers@vyos.io> # -# Copyright (C) 2024 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 program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, +# 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 General Public License for more details. +# 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 General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. # Migrating to named ipv6 pools -from sys import argv -from sys import exit from vyos.configtree import ConfigTree -if len(argv) < 2: - print("Must specify file name!") - exit(1) +base = ['service', 'ipoe-server'] +pool_base = base + ['client-ipv6-pool'] -file_name = argv[1] +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + return -with open(file_name, 'r') as f: - config_file = f.read() + if not config.exists(pool_base): + return -config = ConfigTree(config_file) -base = ['service', 'ipoe-server'] -pool_base = base + ['client-ipv6-pool'] -if not config.exists(base): - exit(0) - -if not config.exists(pool_base): - exit(0) - -ipv6_pool_name = 'ipv6-pool' -config.copy(pool_base, pool_base + [ipv6_pool_name]) - -if config.exists(pool_base + ['prefix']): - config.delete(pool_base + ['prefix']) - config.set(base + ['default-ipv6-pool'], value=ipv6_pool_name) -if config.exists(pool_base + ['delegate']): - config.delete(pool_base + ['delegate']) - -# format as tag node -config.set_tag(pool_base) - -try: - with open(file_name, 'w') as f: - f.write(config.to_string()) -except OSError as e: - print("Failed to save the modified config: {}".format(e)) - exit(1) + ipv6_pool_name = 'ipv6-pool' + config.copy(pool_base, pool_base + [ipv6_pool_name]) + + if config.exists(pool_base + ['prefix']): + config.delete(pool_base + ['prefix']) + config.set(base + ['default-ipv6-pool'], value=ipv6_pool_name) + if config.exists(pool_base + ['delegate']): + config.delete(pool_base + ['delegate']) + + # format as tag node + config.set_tag(pool_base) diff --git a/src/migration-scripts/ipsec/10-to-11 b/src/migration-scripts/ipsec/10-to-11 old mode 100755 new mode 100644 index 509216267..6c4ccb553 --- a/src/migration-scripts/ipsec/10-to-11 +++ b/src/migration-scripts/ipsec/10-to-11 @@ -1,83 +1,63 @@ -#!/usr/bin/env python3 +# Copyright 2023-2024 VyOS maintainers and contributors <maintainers@vyos.io> # -# Copyright (C) 2023 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 program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, +# 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 General Public License for more details. +# 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 General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. -from sys import argv -from sys import exit +# T4916: Rewrite IPsec peer authentication and psk migration from vyos.configtree import ConfigTree - -if len(argv) < 2: - print("Must specify file name!") - exit(1) - -file_name = argv[1] - -with open(file_name, 'r') as f: - config_file = f.read() - base = ['vpn', 'ipsec'] -config = ConfigTree(config_file) - -if not config.exists(base): - # Nothing to do - exit(0) - -# PEER changes -if config.exists(base + ['site-to-site', 'peer']): - for peer in config.list_nodes(base + ['site-to-site', 'peer']): - peer_base = base + ['site-to-site', 'peer', peer] - - # replace: 'ipsec site-to-site peer <tag> authentication pre-shared-secret xxx' - # => 'ipsec authentication psk <tag> secret xxx' - if config.exists(peer_base + ['authentication', 'pre-shared-secret']): - tmp = config.return_value(peer_base + ['authentication', 'pre-shared-secret']) - config.delete(peer_base + ['authentication', 'pre-shared-secret']) - config.set(base + ['authentication', 'psk', peer, 'secret'], value=tmp) - # format as tag node to avoid loading problems - config.set_tag(base + ['authentication', 'psk']) - - # Get id's from peers for "ipsec auth psk <tag> id xxx" - if config.exists(peer_base + ['authentication', 'local-id']): - local_id = config.return_value(peer_base + ['authentication', 'local-id']) - config.set(base + ['authentication', 'psk', peer, 'id'], value=local_id, replace=False) - if config.exists(peer_base + ['authentication', 'remote-id']): - remote_id = config.return_value(peer_base + ['authentication', 'remote-id']) - config.set(base + ['authentication', 'psk', peer, 'id'], value=remote_id, replace=False) - - if config.exists(peer_base + ['local-address']): - tmp = config.return_value(peer_base + ['local-address']) - config.set(base + ['authentication', 'psk', peer, 'id'], value=tmp, replace=False) - if config.exists(peer_base + ['remote-address']): - tmp = config.return_values(peer_base + ['remote-address']) - if tmp: - for remote_addr in tmp: - if remote_addr == 'any': - remote_addr = '%any' - config.set(base + ['authentication', 'psk', peer, 'id'], value=remote_addr, replace=False) - - # get DHCP peer interface as psk dhcp-interface - if config.exists(peer_base + ['dhcp-interface']): - tmp = config.return_value(peer_base + ['dhcp-interface']) - config.set(base + ['authentication', 'psk', peer, 'dhcp-interface'], value=tmp) - -try: - with open(file_name, 'w') as f: - f.write(config.to_string()) -except OSError as e: - print(f'Failed to save the modified config: {e}') - exit(1) +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + # Nothing to do + return + + # PEER changes + if config.exists(base + ['site-to-site', 'peer']): + for peer in config.list_nodes(base + ['site-to-site', 'peer']): + peer_base = base + ['site-to-site', 'peer', peer] + + # replace: 'ipsec site-to-site peer <tag> authentication pre-shared-secret xxx' + # => 'ipsec authentication psk <tag> secret xxx' + if config.exists(peer_base + ['authentication', 'pre-shared-secret']): + tmp = config.return_value(peer_base + ['authentication', 'pre-shared-secret']) + config.delete(peer_base + ['authentication', 'pre-shared-secret']) + config.set(base + ['authentication', 'psk', peer, 'secret'], value=tmp) + # format as tag node to avoid loading problems + config.set_tag(base + ['authentication', 'psk']) + + # Get id's from peers for "ipsec auth psk <tag> id xxx" + if config.exists(peer_base + ['authentication', 'local-id']): + local_id = config.return_value(peer_base + ['authentication', 'local-id']) + config.set(base + ['authentication', 'psk', peer, 'id'], value=local_id, replace=False) + if config.exists(peer_base + ['authentication', 'remote-id']): + remote_id = config.return_value(peer_base + ['authentication', 'remote-id']) + config.set(base + ['authentication', 'psk', peer, 'id'], value=remote_id, replace=False) + + if config.exists(peer_base + ['local-address']): + tmp = config.return_value(peer_base + ['local-address']) + config.set(base + ['authentication', 'psk', peer, 'id'], value=tmp, replace=False) + if config.exists(peer_base + ['remote-address']): + tmp = config.return_values(peer_base + ['remote-address']) + if tmp: + for remote_addr in tmp: + if remote_addr == 'any': + remote_addr = '%any' + config.set(base + ['authentication', 'psk', peer, 'id'], value=remote_addr, replace=False) + + # get DHCP peer interface as psk dhcp-interface + if config.exists(peer_base + ['dhcp-interface']): + tmp = config.return_value(peer_base + ['dhcp-interface']) + config.set(base + ['authentication', 'psk', peer, 'dhcp-interface'], value=tmp) diff --git a/src/migration-scripts/ipsec/11-to-12 b/src/migration-scripts/ipsec/11-to-12 old mode 100755 new mode 100644 index 4833d0876..fc65f1825 --- a/src/migration-scripts/ipsec/11-to-12 +++ b/src/migration-scripts/ipsec/11-to-12 @@ -1,51 +1,31 @@ -#!/usr/bin/env python3 +# Copyright 2023-2024 VyOS maintainers and contributors <maintainers@vyos.io> # -# Copyright (C) 2023-2024 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 program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, +# 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 General Public License for more details. +# 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 General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. # Remove legacy ipsec.conf and ipsec.secrets - Not supported with swanctl -from sys import argv -from sys import exit - from vyos.configtree import ConfigTree -if len(argv) < 2: - print("Must specify file name!") - exit(1) - -file_name = argv[1] - -with open(file_name, 'r') as f: - config_file = f.read() - base = ['vpn', 'ipsec'] -config = ConfigTree(config_file) - -if not config.exists(base): - # Nothing to do - exit(0) -if config.exists(base + ['include-ipsec-conf']): - config.delete(base + ['include-ipsec-conf']) +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + # Nothing to do + return -if config.exists(base + ['include-ipsec-secrets']): - config.delete(base + ['include-ipsec-secrets']) + if config.exists(base + ['include-ipsec-conf']): + config.delete(base + ['include-ipsec-conf']) -try: - with open(file_name, 'w') as f: - f.write(config.to_string()) -except OSError as e: - print(f'Failed to save the modified config: {e}') - exit(1) + if config.exists(base + ['include-ipsec-secrets']): + config.delete(base + ['include-ipsec-secrets']) diff --git a/src/migration-scripts/ipsec/12-to-13 b/src/migration-scripts/ipsec/12-to-13 old mode 100755 new mode 100644 index d90c70314..ffe766eb2 --- a/src/migration-scripts/ipsec/12-to-13 +++ b/src/migration-scripts/ipsec/12-to-13 @@ -1,57 +1,37 @@ -#!/usr/bin/env python3 +# Copyright 2024 VyOS maintainers and contributors <maintainers@vyos.io> # -# Copyright (C) 2024 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 program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, +# 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 General Public License for more details. +# 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 General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. # Changed value of dead-peer-detection.action from hold to trap # Changed value of close-action from hold to trap and from restart to start -from sys import argv -from sys import exit - from vyos.configtree import ConfigTree -if len(argv) < 2: - print("Must specify file name!") - exit(1) - -file_name = argv[1] - -with open(file_name, 'r') as f: - config_file = f.read() - base = ['vpn', 'ipsec', 'ike-group'] -config = ConfigTree(config_file) -if not config.exists(base): - # Nothing to do - exit(0) -else: +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + # Nothing to do + return + for ike_group in config.list_nodes(base): base_dpd_action = base + [ike_group, 'dead-peer-detection', 'action'] base_close_action = base + [ike_group, 'close-action'] if config.exists(base_dpd_action) and config.return_value(base_dpd_action) == 'hold': config.set(base_dpd_action, 'trap', replace=True) if config.exists(base_close_action): if config.return_value(base_close_action) == 'hold': config.set(base_close_action, 'trap', replace=True) if config.return_value(base_close_action) == 'restart': config.set(base_close_action, 'start', replace=True) - -try: - with open(file_name, 'w') as f: - f.write(config.to_string()) -except OSError as e: - print(f'Failed to save the modified config: {e}') - exit(1) diff --git a/src/migration-scripts/ipsec/4-to-5 b/src/migration-scripts/ipsec/4-to-5 old mode 100755 new mode 100644 index 772d05787..a88a543d3 --- a/src/migration-scripts/ipsec/4-to-5 +++ b/src/migration-scripts/ipsec/4-to-5 @@ -1,47 +1,28 @@ -#!/usr/bin/env python3 +# Copyright 2019-2024 VyOS maintainers and contributors <maintainers@vyos.io> # -# Copyright (C) 2019 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 program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, +# 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 General Public License for more details. +# 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 General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. # log-modes have changed, keyword all to any -import sys - from vyos.configtree import ConfigTree -if len(sys.argv) < 2: - print("Must specify file name!") - sys.exit(1) - -file_name = sys.argv[1] - -with open(file_name, 'r') as f: - config_file = f.read() - -ctree = ConfigTree(config_file) - -if not ctree.exists(['vpn', 'ipsec', 'logging','log-modes']): - # Nothing to do - sys.exit(0) -else: - lmodes = ctree.return_values(['vpn', 'ipsec', 'logging','log-modes']) - for mode in lmodes: - if mode == 'all': - ctree.set(['vpn', 'ipsec', 'logging','log-modes'], value='any', replace=True) +def migrate(config: ConfigTree) -> None: + if not config.exists(['vpn', 'ipsec', 'logging','log-modes']): + # Nothing to do + return - try: - open(file_name,'w').write(ctree.to_string()) - except OSError as e: - print("Failed to save the modified config: {}".format(e)) - sys.exit(1) + lmodes = config.return_values(['vpn', 'ipsec', 'logging','log-modes']) + for mode in lmodes: + if mode == 'all': + config.set(['vpn', 'ipsec', 'logging','log-modes'], value='any', replace=True) diff --git a/src/migration-scripts/ipsec/5-to-6 b/src/migration-scripts/ipsec/5-to-6 old mode 100755 new mode 100644 index 7d7c777c6..373428d61 --- a/src/migration-scripts/ipsec/5-to-6 +++ b/src/migration-scripts/ipsec/5-to-6 @@ -1,93 +1,73 @@ -#!/usr/bin/env python3 +# Copyright 2021-2024 VyOS maintainers and contributors <maintainers@vyos.io> # -# Copyright (C) 2021 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 program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, +# 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 General Public License for more details. +# 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 General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. # Remove deprecated strongSwan options from VyOS CLI # - vpn ipsec nat-traversal enable # - vpn ipsec nat-networks allowed-network -from sys import argv -from sys import exit - from vyos.configtree import ConfigTree -if len(argv) < 2: - print("Must specify file name!") - exit(1) - -file_name = argv[1] - -with open(file_name, 'r') as f: - config_file = f.read() - base = ['vpn', 'ipsec'] -config = ConfigTree(config_file) - -if not config.exists(base): - # Nothing to do - exit(0) - -# Delete CLI nodes whose config options got removed by strongSwan -for cli_node in ['nat-traversal', 'nat-networks']: - if config.exists(base + [cli_node]): - config.delete(base + [cli_node]) - -# Remove options only valid in Openswan -if config.exists(base + ['site-to-site', 'peer']): - for peer in config.list_nodes(base + ['site-to-site', 'peer']): - if not config.exists(base + ['site-to-site', 'peer', peer, 'tunnel']): - continue - for tunnel in config.list_nodes(base + ['site-to-site', 'peer', peer, 'tunnel']): - # allow-public-networks - Sets a value in ipsec.conf that was only ever valid in Openswan on kernel 2.6 - nat_networks = base + ['site-to-site', 'peer', peer, 'tunnel', tunnel, 'allow-nat-networks'] - if config.exists(nat_networks): - config.delete(nat_networks) - - # allow-nat-networks - Also sets a value only valid in Openswan - public_networks = base + ['site-to-site', 'peer', peer, 'tunnel', tunnel, 'allow-public-networks'] - if config.exists(public_networks): - config.delete(public_networks) - -# Rename "logging log-level" and "logging log-modes" to something more human friendly -log = base + ['logging'] -if config.exists(log): - config.rename(log, 'log') - log = base + ['log'] - -log_level = log + ['log-level'] -if config.exists(log_level): - config.rename(log_level, 'level') - -log_mode = log + ['log-modes'] -if config.exists(log_mode): - config.rename(log_mode, 'subsystem') - -# Rename "ipsec-interfaces interface" to "interface" -base_interfaces = base + ['ipsec-interfaces', 'interface'] -if config.exists(base_interfaces): - config.copy(base_interfaces, base + ['interface']) - config.delete(base + ['ipsec-interfaces']) - -# Remove deprecated "auto-update" option -tmp = base + ['auto-update'] -if config.exists(tmp): - config.delete(tmp) -try: - with open(file_name, 'w') as f: - f.write(config.to_string()) -except OSError as e: - print(f'Failed to save the modified config: {e}') - exit(1) +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + # Nothing to do + return + + # Delete CLI nodes whose config options got removed by strongSwan + for cli_node in ['nat-traversal', 'nat-networks']: + if config.exists(base + [cli_node]): + config.delete(base + [cli_node]) + + # Remove options only valid in Openswan + if config.exists(base + ['site-to-site', 'peer']): + for peer in config.list_nodes(base + ['site-to-site', 'peer']): + if not config.exists(base + ['site-to-site', 'peer', peer, 'tunnel']): + continue + for tunnel in config.list_nodes(base + ['site-to-site', 'peer', peer, 'tunnel']): + # allow-public-networks - Sets a value in ipsec.conf that was only ever valid in Openswan on kernel 2.6 + nat_networks = base + ['site-to-site', 'peer', peer, 'tunnel', tunnel, 'allow-nat-networks'] + if config.exists(nat_networks): + config.delete(nat_networks) + + # allow-nat-networks - Also sets a value only valid in Openswan + public_networks = base + ['site-to-site', 'peer', peer, 'tunnel', tunnel, 'allow-public-networks'] + if config.exists(public_networks): + config.delete(public_networks) + + # Rename "logging log-level" and "logging log-modes" to something more human friendly + log = base + ['logging'] + if config.exists(log): + config.rename(log, 'log') + log = base + ['log'] + + log_level = log + ['log-level'] + if config.exists(log_level): + config.rename(log_level, 'level') + + log_mode = log + ['log-modes'] + if config.exists(log_mode): + config.rename(log_mode, 'subsystem') + + # Rename "ipsec-interfaces interface" to "interface" + base_interfaces = base + ['ipsec-interfaces', 'interface'] + if config.exists(base_interfaces): + config.copy(base_interfaces, base + ['interface']) + config.delete(base + ['ipsec-interfaces']) + + # Remove deprecated "auto-update" option + tmp = base + ['auto-update'] + if config.exists(tmp): + config.delete(tmp) diff --git a/src/migration-scripts/ipsec/6-to-7 b/src/migration-scripts/ipsec/6-to-7 old mode 100755 new mode 100644 index f8b6de560..5679477c0 --- a/src/migration-scripts/ipsec/6-to-7 +++ b/src/migration-scripts/ipsec/6-to-7 @@ -1,169 +1,155 @@ -#!/usr/bin/env python3 +# Copyright 2021-2024 VyOS maintainers and contributors <maintainers@vyos.io> # -# Copyright (C) 2021-2023 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 program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, +# 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 General Public License for more details. +# 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 General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. # Migrate /config/auth certificates and keys into PKI configuration import os -from sys import argv -from sys import exit - from vyos.configtree import ConfigTree from vyos.pki import load_certificate from vyos.pki import load_crl from vyos.pki import load_private_key from vyos.pki import encode_certificate from vyos.pki import encode_private_key from vyos.utils.process import run -if len(argv) < 2: - print("Must specify file name!") - exit(1) - -file_name = argv[1] - -with open(file_name, 'r') as f: - config_file = f.read() - pki_base = ['pki'] ipsec_site_base = ['vpn', 'ipsec', 'site-to-site', 'peer'] -config = ConfigTree(config_file) -changes_made = False - AUTH_DIR = '/config/auth' def wrapped_pem_to_config_value(pem): return "".join(pem.strip().split("\n")[1:-1]) -if config.exists(ipsec_site_base): +def migrate(config: ConfigTree) -> None: + if not config.exists(ipsec_site_base): + return + + migration_needed = False + for peer in config.list_nodes(ipsec_site_base): + if config.exists(ipsec_site_base + [peer, 'authentication', 'x509']): + migration_needed = True + break + + if not migration_needed: + return + config.set(pki_base + ['ca']) config.set_tag(pki_base + ['ca']) config.set(pki_base + ['certificate']) config.set_tag(pki_base + ['certificate']) for peer in config.list_nodes(ipsec_site_base): if not config.exists(ipsec_site_base + [peer, 'authentication', 'x509']): continue - changes_made = True - peer_x509_base = ipsec_site_base + [peer, 'authentication', 'x509'] pki_name = 'peer_' + peer.replace(".", "-").replace("@", "") if config.exists(peer_x509_base + ['cert-file']): cert_file = config.return_value(peer_x509_base + ['cert-file']) cert_path = os.path.join(AUTH_DIR, cert_file) cert = None if os.path.isfile(cert_path): if not os.access(cert_path, os.R_OK): run(f'sudo chmod 644 {cert_path}') with open(cert_path, 'r') as f: cert_data = f.read() cert = load_certificate(cert_data, wrap_tags=False) if cert: cert_pem = encode_certificate(cert) config.set(pki_base + ['certificate', pki_name, 'certificate'], value=wrapped_pem_to_config_value(cert_pem)) config.set(peer_x509_base + ['certificate'], value=pki_name) else: print(f'Failed to migrate certificate on peer "{peer}"') config.delete(peer_x509_base + ['cert-file']) if config.exists(peer_x509_base + ['ca-cert-file']): ca_cert_file = config.return_value(peer_x509_base + ['ca-cert-file']) ca_cert_path = os.path.join(AUTH_DIR, ca_cert_file) ca_cert = None if os.path.isfile(ca_cert_path): if not os.access(ca_cert_path, os.R_OK): run(f'sudo chmod 644 {ca_cert_path}') with open(ca_cert_path, 'r') as f: ca_cert_data = f.read() ca_cert = load_certificate(ca_cert_data, wrap_tags=False) if ca_cert: ca_cert_pem = encode_certificate(ca_cert) config.set(pki_base + ['ca', pki_name, 'certificate'], value=wrapped_pem_to_config_value(ca_cert_pem)) config.set(peer_x509_base + ['ca-certificate'], value=pki_name) else: print(f'Failed to migrate CA certificate on peer "{peer}"') config.delete(peer_x509_base + ['ca-cert-file']) if config.exists(peer_x509_base + ['crl-file']): crl_file = config.return_value(peer_x509_base + ['crl-file']) crl_path = os.path.join(AUTH_DIR, crl_file) crl = None if os.path.isfile(crl_path): if not os.access(crl_path, os.R_OK): run(f'sudo chmod 644 {crl_path}') with open(crl_path, 'r') as f: crl_data = f.read() crl = load_crl(crl_data, wrap_tags=False) if crl: crl_pem = encode_certificate(crl) config.set(pki_base + ['ca', pki_name, 'crl'], value=wrapped_pem_to_config_value(crl_pem)) else: print(f'Failed to migrate CRL on peer "{peer}"') config.delete(peer_x509_base + ['crl-file']) if config.exists(peer_x509_base + ['key', 'file']): key_file = config.return_value(peer_x509_base + ['key', 'file']) key_passphrase = None if config.exists(peer_x509_base + ['key', 'password']): key_passphrase = config.return_value(peer_x509_base + ['key', 'password']) key_path = os.path.join(AUTH_DIR, key_file) key = None if os.path.isfile(key_path): if not os.access(key_path, os.R_OK): run(f'sudo chmod 644 {key_path}') with open(key_path, 'r') as f: key_data = f.read() key = load_private_key(key_data, passphrase=key_passphrase, wrap_tags=False) if key: key_pem = encode_private_key(key, passphrase=key_passphrase) config.set(pki_base + ['certificate', pki_name, 'private', 'key'], value=wrapped_pem_to_config_value(key_pem)) if key_passphrase: config.set(pki_base + ['certificate', pki_name, 'private', 'password-protected']) config.set(peer_x509_base + ['private-key-passphrase'], value=key_passphrase) else: print(f'Failed to migrate private key on peer "{peer}"') config.delete(peer_x509_base + ['key']) - -if changes_made: - try: - with open(file_name, 'w') as f: - f.write(config.to_string()) - except OSError as e: - print("Failed to save the modified config: {}".format(e)) - sys.exit(1) diff --git a/src/migration-scripts/ipsec/7-to-8 b/src/migration-scripts/ipsec/7-to-8 old mode 100755 new mode 100644 index 9acc737d5..481f00d29 --- a/src/migration-scripts/ipsec/7-to-8 +++ b/src/migration-scripts/ipsec/7-to-8 @@ -1,124 +1,103 @@ -#!/usr/bin/env python3 +# Copyright 2021-2024 VyOS maintainers and contributors <maintainers@vyos.io> # -# Copyright (C) 2021-2024 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 program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, +# 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 General Public License for more details. +# 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 General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. # Migrate rsa keys into PKI configuration import base64 import os import struct from cryptography.hazmat.primitives.asymmetric import rsa -from sys import argv -from sys import exit - from vyos.configtree import ConfigTree from vyos.pki import load_private_key from vyos.pki import encode_public_key from vyos.pki import encode_private_key -if len(argv) < 2: - print("Must specify file name!") - exit(1) - -file_name = argv[1] - -with open(file_name, 'r') as f: - config_file = f.read() - pki_base = ['pki'] ipsec_site_base = ['vpn', 'ipsec', 'site-to-site', 'peer'] rsa_keys_base = ['vpn', 'rsa-keys'] -config = ConfigTree(config_file) - LOCAL_KEY_PATHS = ['/config/auth/', '/config/ipsec.d/rsa-keys/'] def migrate_from_vyatta_key(data): data = base64.b64decode(data[2:]) length = struct.unpack('B', data[:1])[0] e = int.from_bytes(data[1:1+length], 'big') n = int.from_bytes(data[1+length:], 'big') public_numbers = rsa.RSAPublicNumbers(e, n) return public_numbers.public_key() def wrapped_pem_to_config_value(pem): return "".join(pem.strip().split("\n")[1:-1]) local_key_name = 'localhost' -if config.exists(rsa_keys_base): - if not config.exists(pki_base + ['key-pair']): - config.set(pki_base + ['key-pair']) - config.set_tag(pki_base + ['key-pair']) - - if config.exists(rsa_keys_base + ['local-key', 'file']): - local_file = config.return_value(rsa_keys_base + ['local-key', 'file']) - local_path = None - local_key = None - - for path in LOCAL_KEY_PATHS: - full_path = os.path.join(path, local_file) - if os.path.exists(full_path): - local_path = full_path - break - - if local_path: - with open(local_path, 'r') as f: - local_key_data = f.read() - local_key = load_private_key(local_key_data, wrap_tags=False) - - if local_key: - local_key_pem = encode_private_key(local_key) - config.set(pki_base + ['key-pair', local_key_name, 'private', 'key'], value=wrapped_pem_to_config_value(local_key_pem)) - else: - print('Failed to migrate local RSA key') - - if config.exists(rsa_keys_base + ['rsa-key-name']): - for rsa_name in config.list_nodes(rsa_keys_base + ['rsa-key-name']): - if not config.exists(rsa_keys_base + ['rsa-key-name', rsa_name, 'rsa-key']): - continue +def migrate(config: ConfigTree) -> None: + if config.exists(rsa_keys_base): + if not config.exists(pki_base + ['key-pair']): + config.set(pki_base + ['key-pair']) + config.set_tag(pki_base + ['key-pair']) + + if config.exists(rsa_keys_base + ['local-key', 'file']): + local_file = config.return_value(rsa_keys_base + ['local-key', 'file']) + local_path = None + local_key = None + + for path in LOCAL_KEY_PATHS: + full_path = os.path.join(path, local_file) + if os.path.exists(full_path): + local_path = full_path + break + + if local_path: + with open(local_path, 'r') as f: + local_key_data = f.read() + local_key = load_private_key(local_key_data, wrap_tags=False) + + if local_key: + local_key_pem = encode_private_key(local_key) + config.set(pki_base + ['key-pair', local_key_name, 'private', 'key'], value=wrapped_pem_to_config_value(local_key_pem)) + else: + print('Failed to migrate local RSA key') - vyatta_key = config.return_value(rsa_keys_base + ['rsa-key-name', rsa_name, 'rsa-key']) - public_key = migrate_from_vyatta_key(vyatta_key) + if config.exists(rsa_keys_base + ['rsa-key-name']): + for rsa_name in config.list_nodes(rsa_keys_base + ['rsa-key-name']): + if not config.exists(rsa_keys_base + ['rsa-key-name', rsa_name, 'rsa-key']): + continue - if public_key: - public_key_pem = encode_public_key(public_key) - config.set(pki_base + ['key-pair', rsa_name, 'public', 'key'], value=wrapped_pem_to_config_value(public_key_pem)) - else: - print(f'Failed to migrate rsa-key "{rsa_name}"') + vyatta_key = config.return_value(rsa_keys_base + ['rsa-key-name', rsa_name, 'rsa-key']) + public_key = migrate_from_vyatta_key(vyatta_key) - config.delete(rsa_keys_base) + if public_key: + public_key_pem = encode_public_key(public_key) + config.set(pki_base + ['key-pair', rsa_name, 'public', 'key'], value=wrapped_pem_to_config_value(public_key_pem)) + else: + print(f'Failed to migrate rsa-key "{rsa_name}"') -if config.exists(ipsec_site_base): - for peer in config.list_nodes(ipsec_site_base): - mode = config.return_value(ipsec_site_base + [peer, 'authentication', 'mode']) + config.delete(rsa_keys_base) - if mode != 'rsa': - continue + if config.exists(ipsec_site_base): + for peer in config.list_nodes(ipsec_site_base): + mode = config.return_value(ipsec_site_base + [peer, 'authentication', 'mode']) - config.set(ipsec_site_base + [peer, 'authentication', 'rsa', 'local-key'], value=local_key_name) + if mode != 'rsa': + continue - remote_key_name = config.return_value(ipsec_site_base + [peer, 'authentication', 'rsa-key-name']) - config.set(ipsec_site_base + [peer, 'authentication', 'rsa', 'remote-key'], value=remote_key_name) - config.delete(ipsec_site_base + [peer, 'authentication', 'rsa-key-name']) + config.set(ipsec_site_base + [peer, 'authentication', 'rsa', 'local-key'], value=local_key_name) -try: - with open(file_name, 'w') as f: - f.write(config.to_string()) -except OSError as e: - print("Failed to save the modified config: {}".format(e)) - sys.exit(1) + remote_key_name = config.return_value(ipsec_site_base + [peer, 'authentication', 'rsa-key-name']) + config.set(ipsec_site_base + [peer, 'authentication', 'rsa', 'remote-key'], value=remote_key_name) + config.delete(ipsec_site_base + [peer, 'authentication', 'rsa-key-name']) diff --git a/src/migration-scripts/ipsec/8-to-9 b/src/migration-scripts/ipsec/8-to-9 old mode 100755 new mode 100644 index c08411f83..7f325139f --- a/src/migration-scripts/ipsec/8-to-9 +++ b/src/migration-scripts/ipsec/8-to-9 @@ -1,48 +1,30 @@ -#!/usr/bin/env python3 +# Copyright 2022-2024 VyOS maintainers and contributors <maintainers@vyos.io> # -# Copyright (C) 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 program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, +# 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 General Public License for more details. +# 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 General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. -from sys import argv -from sys import exit +# T4288 : close-action is missing in swanctl.conf from vyos.configtree import ConfigTree -if len(argv) < 2: - print("Must specify file name!") - exit(1) - -file_name = argv[1] - -with open(file_name, 'r') as f: - config_file = f.read() - base = ['vpn', 'ipsec', 'ike-group'] -config = ConfigTree(config_file) -if not config.exists(base): - # Nothing to do - exit(0) -else: +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + # Nothing to do + return + for ike_group in config.list_nodes(base): base_closeaction = base + [ike_group, 'close-action'] if config.exists(base_closeaction) and config.return_value(base_closeaction) == 'clear': config.set(base_closeaction, 'none', replace=True) - -try: - with open(file_name, 'w') as f: - f.write(config.to_string()) -except OSError as e: - print(f'Failed to save the modified config: {e}') - exit(1) diff --git a/src/migration-scripts/ipsec/9-to-10 b/src/migration-scripts/ipsec/9-to-10 old mode 100755 new mode 100644 index bc10e1997..321a75973 --- a/src/migration-scripts/ipsec/9-to-10 +++ b/src/migration-scripts/ipsec/9-to-10 @@ -1,131 +1,114 @@ -#!/usr/bin/env python3 +# Copyright 2022-2024 VyOS maintainers and contributors <maintainers@vyos.io> # -# Copyright (C) 2022-2024 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 program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, +# 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 General Public License for more details. +# 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 General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. -import re +# T4118: Change vpn ipsec syntax for IKE ESP and peer +# T4879: IPsec migration script remote-id for peer name eq address -from sys import argv -from sys import exit +import re from vyos.configtree import ConfigTree -if len(argv) < 2: - print("Must specify file name!") - exit(1) - -file_name = argv[1] - -with open(file_name, 'r') as f: - config_file = f.read() - base = ['vpn', 'ipsec'] -config = ConfigTree(config_file) - -if not config.exists(base): - # Nothing to do - exit(0) - -# IKE changes, T4118: -if config.exists(base + ['ike-group']): - for ike_group in config.list_nodes(base + ['ike-group']): - # replace 'ipsec ike-group <tag> mobike disable' - # => 'ipsec ike-group <tag> disable-mobike' - mobike = base + ['ike-group', ike_group, 'mobike'] - if config.exists(mobike): - if config.return_value(mobike) == 'disable': - config.set(base + ['ike-group', ike_group, 'disable-mobike']) - config.delete(mobike) - - # replace 'ipsec ike-group <tag> ikev2-reauth yes' - # => 'ipsec ike-group <tag> ikev2-reauth' - reauth = base + ['ike-group', ike_group, 'ikev2-reauth'] - if config.exists(reauth): - if config.return_value(reauth) == 'yes': - config.delete(reauth) - config.set(reauth) - else: - config.delete(reauth) - -# ESP changes -# replace 'ipsec esp-group <tag> compression enable' -# => 'ipsec esp-group <tag> compression' -if config.exists(base + ['esp-group']): - for esp_group in config.list_nodes(base + ['esp-group']): - compression = base + ['esp-group', esp_group, 'compression'] - if config.exists(compression): - if config.return_value(compression) == 'enable': - config.delete(compression) - config.set(compression) - else: - config.delete(compression) - -# PEER changes -if config.exists(base + ['site-to-site', 'peer']): - for peer in config.list_nodes(base + ['site-to-site', 'peer']): - peer_base = base + ['site-to-site', 'peer', peer] - - # replace: 'peer <tag> id x' - # => 'peer <tag> local-id x' - if config.exists(peer_base + ['authentication', 'id']): - config.rename(peer_base + ['authentication', 'id'], 'local-id') - - # For the peer '@foo' set remote-id 'foo' if remote-id is not defined - # For the peer '192.0.2.1' set remote-id '192.0.2.1' if remote-id is not defined - if not config.exists(peer_base + ['authentication', 'remote-id']): - tmp = peer.replace('@', '') if peer.startswith('@') else peer - config.set(peer_base + ['authentication', 'remote-id'], value=tmp) - - # replace: 'peer <tag> force-encapsulation enable' - # => 'peer <tag> force-udp-encapsulation' - force_enc = peer_base + ['force-encapsulation'] - if config.exists(force_enc): - if config.return_value(force_enc) == 'enable': - config.delete(force_enc) - config.set(peer_base + ['force-udp-encapsulation']) - else: - config.delete(force_enc) - - # add option: 'peer <tag> remote-address x.x.x.x' - remote_address = peer - if peer.startswith('@'): - remote_address = 'any' - config.set(peer_base + ['remote-address'], value=remote_address) - # Peer name it is swanctl connection name and shouldn't contain dots or colons - # rename peer: - # peer 192.0.2.1 => peer peer_192-0-2-1 - # peer 2001:db8::2 => peer peer_2001-db8--2 - # peer @foo => peer peer_foo - re_peer_name = re.sub(':|\.', '-', peer) - if re_peer_name.startswith('@'): - re_peer_name = re.sub('@', '', re_peer_name) - new_peer_name = f'peer_{re_peer_name}' - - config.rename(peer_base, new_peer_name) - -# remote-access/road-warrior changes -if config.exists(base + ['remote-access', 'connection']): - for connection in config.list_nodes(base + ['remote-access', 'connection']): - ra_base = base + ['remote-access', 'connection', connection] - # replace: 'remote-access connection <tag> authentication id x' - # => 'remote-access connection <tag> authentication local-id x' - if config.exists(ra_base + ['authentication', 'id']): - config.rename(ra_base + ['authentication', 'id'], 'local-id') -try: - with open(file_name, 'w') as f: - f.write(config.to_string()) -except OSError as e: - print(f'Failed to save the modified config: {e}') - exit(1) +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + # Nothing to do + return + + # IKE changes, T4118: + if config.exists(base + ['ike-group']): + for ike_group in config.list_nodes(base + ['ike-group']): + # replace 'ipsec ike-group <tag> mobike disable' + # => 'ipsec ike-group <tag> disable-mobike' + mobike = base + ['ike-group', ike_group, 'mobike'] + if config.exists(mobike): + if config.return_value(mobike) == 'disable': + config.set(base + ['ike-group', ike_group, 'disable-mobike']) + config.delete(mobike) + + # replace 'ipsec ike-group <tag> ikev2-reauth yes' + # => 'ipsec ike-group <tag> ikev2-reauth' + reauth = base + ['ike-group', ike_group, 'ikev2-reauth'] + if config.exists(reauth): + if config.return_value(reauth) == 'yes': + config.delete(reauth) + config.set(reauth) + else: + config.delete(reauth) + + # ESP changes + # replace 'ipsec esp-group <tag> compression enable' + # => 'ipsec esp-group <tag> compression' + if config.exists(base + ['esp-group']): + for esp_group in config.list_nodes(base + ['esp-group']): + compression = base + ['esp-group', esp_group, 'compression'] + if config.exists(compression): + if config.return_value(compression) == 'enable': + config.delete(compression) + config.set(compression) + else: + config.delete(compression) + + # PEER changes + if config.exists(base + ['site-to-site', 'peer']): + for peer in config.list_nodes(base + ['site-to-site', 'peer']): + peer_base = base + ['site-to-site', 'peer', peer] + + # replace: 'peer <tag> id x' + # => 'peer <tag> local-id x' + if config.exists(peer_base + ['authentication', 'id']): + config.rename(peer_base + ['authentication', 'id'], 'local-id') + + # For the peer '@foo' set remote-id 'foo' if remote-id is not defined + # For the peer '192.0.2.1' set remote-id '192.0.2.1' if remote-id is not defined + if not config.exists(peer_base + ['authentication', 'remote-id']): + tmp = peer.replace('@', '') if peer.startswith('@') else peer + config.set(peer_base + ['authentication', 'remote-id'], value=tmp) + + # replace: 'peer <tag> force-encapsulation enable' + # => 'peer <tag> force-udp-encapsulation' + force_enc = peer_base + ['force-encapsulation'] + if config.exists(force_enc): + if config.return_value(force_enc) == 'enable': + config.delete(force_enc) + config.set(peer_base + ['force-udp-encapsulation']) + else: + config.delete(force_enc) + + # add option: 'peer <tag> remote-address x.x.x.x' + remote_address = peer + if peer.startswith('@'): + remote_address = 'any' + config.set(peer_base + ['remote-address'], value=remote_address) + # Peer name it is swanctl connection name and shouldn't contain dots or colons + # rename peer: + # peer 192.0.2.1 => peer peer_192-0-2-1 + # peer 2001:db8::2 => peer peer_2001-db8--2 + # peer @foo => peer peer_foo + re_peer_name = re.sub(':|\.', '-', peer) + if re_peer_name.startswith('@'): + re_peer_name = re.sub('@', '', re_peer_name) + new_peer_name = f'peer_{re_peer_name}' + + config.rename(peer_base, new_peer_name) + + # remote-access/road-warrior changes + if config.exists(base + ['remote-access', 'connection']): + for connection in config.list_nodes(base + ['remote-access', 'connection']): + ra_base = base + ['remote-access', 'connection', connection] + # replace: 'remote-access connection <tag> authentication id x' + # => 'remote-access connection <tag> authentication local-id x' + if config.exists(ra_base + ['authentication', 'id']): + config.rename(ra_base + ['authentication', 'id'], 'local-id') diff --git a/src/migration-scripts/isis/0-to-1 b/src/migration-scripts/isis/0-to-1 old mode 100755 new mode 100644 index 0149c0c1f..e24288558 --- a/src/migration-scripts/isis/0-to-1 +++ b/src/migration-scripts/isis/0-to-1 @@ -1,56 +1,36 @@ -#!/usr/bin/env python3 +# Copyright 2021-2024 VyOS maintainers and contributors <maintainers@vyos.io> # -# Copyright (C) 2021 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 program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, +# 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 General Public License for more details. +# 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 General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. # T3417: migrate IS-IS tagNode to node as we can only have one IS-IS process -from sys import argv -from sys import exit - from vyos.configtree import ConfigTree -if len(argv) < 2: - print("Must specify file name!") - exit(1) - -file_name = argv[1] - -with open(file_name, 'r') as f: - config_file = f.read() - base = ['protocols', 'isis'] -config = ConfigTree(config_file) - -if not config.exists(base): - # Nothing to do - exit(0) -# We need a temporary copy of the config -tmp_base = ['protocols', 'isis2'] -config.copy(base, tmp_base) +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + # Nothing to do + return -# Now it's save to delete the old configuration -config.delete(base) + # We need a temporary copy of the config + tmp_base = ['protocols', 'isis2'] + config.copy(base, tmp_base) -# Rename temporary copy to new final config (IS-IS domain key is static and no -# longer required to be set via CLI) -config.rename(tmp_base, 'isis') + # Now it's save to delete the old configuration + config.delete(base) -try: - with open(file_name, 'w') as f: - f.write(config.to_string()) -except OSError as e: - print(f'Failed to save the modified config: {e}') - exit(1) + # Rename temporary copy to new final config (IS-IS domain key is static and no + # longer required to be set via CLI) + config.rename(tmp_base, 'isis') diff --git a/src/migration-scripts/isis/1-to-2 b/src/migration-scripts/isis/1-to-2 old mode 100755 new mode 100644 index 9c110bf2a..0fc92a6de --- a/src/migration-scripts/isis/1-to-2 +++ b/src/migration-scripts/isis/1-to-2 @@ -1,46 +1,27 @@ -#!/usr/bin/env python3 +# Copyright 2022-2024 VyOS maintainers and contributors <maintainers@vyos.io> # -# Copyright (C) 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 program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, +# 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 General Public License for more details. +# 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 General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. # T4739 refactor, and remove "on" from segment routing from the configuration -from sys import argv -from sys import exit - from vyos.configtree import ConfigTree -if len(argv) < 2: - print("Must specify file name!") - exit(1) - -file_name = argv[1] - -with open(file_name, 'r') as f: - config_file = f.read() - -config = ConfigTree(config_file) - -# Check if ISIS segment routing is configured. Then check if segment routing "on" exists, then delete the "on" as it is no longer needed. This is for global configuration. -if config.exists(['protocols', 'isis']): - if config.exists(['protocols', 'isis', 'segment-routing']): - if config.exists(['protocols', 'isis', 'segment-routing', 'enable']): - config.delete(['protocols', 'isis', 'segment-routing', 'enable']) - -try: - with open(file_name, 'w') as f: - f.write(config.to_string()) -except OSError as e: - print(f'Failed to save the modified config: {e}') - exit(1) +def migrate(config: ConfigTree) -> None: + # Check if ISIS segment routing is configured. Then check if segment + # routing "on" exists, then delete the "on" as it is no longer needed. + # This is for global configuration. + if config.exists(['protocols', 'isis']): + if config.exists(['protocols', 'isis', 'segment-routing']): + if config.exists(['protocols', 'isis', 'segment-routing', 'enable']): + config.delete(['protocols', 'isis', 'segment-routing', 'enable']) diff --git a/src/migration-scripts/isis/2-to-3 b/src/migration-scripts/isis/2-to-3 old mode 100755 new mode 100644 index 78e3c1715..afb9f2340 --- a/src/migration-scripts/isis/2-to-3 +++ b/src/migration-scripts/isis/2-to-3 @@ -1,63 +1,43 @@ -#!/usr/bin/env python3 +# Copyright 2023-2024 VyOS maintainers and contributors <maintainers@vyos.io> # -# Copyright (C) 2023 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 program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, +# 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 General Public License for more details. +# 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 General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. # T5150: Rework CLI definitions to apply route-maps between routing daemons # and zebra/kernel -from sys import argv -from sys import exit - from vyos.configtree import ConfigTree -if len(argv) < 2: - print("Must specify file name!") - exit(1) - -file_name = argv[1] - -with open(file_name, 'r') as f: - config_file = f.read() - -config = ConfigTree(config_file) - isis_base = ['protocols', 'isis'] -# Check if IS-IS is configured - if so, migrate the CLI node -if config.exists(isis_base): - if config.exists(isis_base + ['route-map']): - tmp = config.return_value(isis_base + ['route-map']) - - config.set(['system', 'ip', 'protocol', 'isis', 'route-map'], value=tmp) - config.set_tag(['system', 'ip', 'protocol']) - config.delete(isis_base + ['route-map']) - -# Check if vrf names are configured. Check if IS-IS is configured - if so, -# migrate the CLI node(s) -if config.exists(['vrf', 'name']): - for vrf in config.list_nodes(['vrf', 'name']): - vrf_base = ['vrf', 'name', vrf] - if config.exists(vrf_base + ['protocols', 'isis', 'route-map']): - tmp = config.return_value(vrf_base + ['protocols', 'isis', 'route-map']) - - config.set(vrf_base + ['ip', 'protocol', 'isis', 'route-map'], value=tmp) - config.set_tag(vrf_base + ['ip', 'protocol', 'isis']) - config.delete(vrf_base + ['protocols', 'isis', 'route-map']) -try: - with open(file_name, 'w') as f: - f.write(config.to_string()) -except OSError as e: - print(f'Failed to save the modified config: {e}') - exit(1) +def migrate(config: ConfigTree) -> None: + # Check if IS-IS is configured - if so, migrate the CLI node + if config.exists(isis_base): + if config.exists(isis_base + ['route-map']): + tmp = config.return_value(isis_base + ['route-map']) + + config.set(['system', 'ip', 'protocol', 'isis', 'route-map'], value=tmp) + config.set_tag(['system', 'ip', 'protocol']) + config.delete(isis_base + ['route-map']) + + # Check if vrf names are configured. Check if IS-IS is configured - if so, + # migrate the CLI node(s) + if config.exists(['vrf', 'name']): + for vrf in config.list_nodes(['vrf', 'name']): + vrf_base = ['vrf', 'name', vrf] + if config.exists(vrf_base + ['protocols', 'isis', 'route-map']): + tmp = config.return_value(vrf_base + ['protocols', 'isis', 'route-map']) + + config.set(vrf_base + ['ip', 'protocol', 'isis', 'route-map'], value=tmp) + config.set_tag(vrf_base + ['ip', 'protocol', 'isis']) + config.delete(vrf_base + ['protocols', 'isis', 'route-map']) diff --git a/src/migration-scripts/l2tp/0-to-1 b/src/migration-scripts/l2tp/0-to-1 old mode 100755 new mode 100644 index 15d229822..f0cb6af96 --- a/src/migration-scripts/l2tp/0-to-1 +++ b/src/migration-scripts/l2tp/0-to-1 @@ -1,60 +1,56 @@ -#!/usr/bin/env python3 - +# Copyright 2018-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# 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 <http://www.gnu.org/licenses/>. + +# T987: Unclutter L2TP/IPSec RADIUS configuration nodes # Unclutter L2TP VPN configuiration - move radius-server top level tag # nodes to a regular node which now also configures the radius source address # used when querying a radius server -import sys - from vyos.configtree import ConfigTree -if len(sys.argv) < 2: - print("Must specify file name!") - sys.exit(1) - -file_name = sys.argv[1] - -with open(file_name, 'r') as f: - config_file = f.read() +cfg_base = ['vpn', 'l2tp', 'remote-access', 'authentication'] -config = ConfigTree(config_file) +def migrate(config: ConfigTree) -> None: + if not config.exists(cfg_base): + # Nothing to do + return -cfg_base = ['vpn', 'l2tp', 'remote-access', 'authentication'] -if not config.exists(cfg_base): - # Nothing to do - sys.exit(0) -else: # Migrate "vpn l2tp authentication radius-source-address" to new # "vpn l2tp authentication radius source-address" if config.exists(cfg_base + ['radius-source-address']): address = config.return_value(cfg_base + ['radius-source-address']) # delete old configuration node config.delete(cfg_base + ['radius-source-address']) # write new configuration node config.set(cfg_base + ['radius', 'source-address'], value=address) # Migrate "vpn l2tp authentication radius-server" tag node to new # "vpn l2tp authentication radius server" tag node if config.exists(cfg_base + ['radius-server']): for server in config.list_nodes(cfg_base + ['radius-server']): base_server = cfg_base + ['radius-server', server] key = config.return_value(base_server + ['key']) # delete old configuration node config.delete(base_server) # write new configuration node config.set(cfg_base + ['radius', 'server', server, 'key'], value=key) # format as tag node config.set_tag(cfg_base + ['radius', 'server']) # delete top level tag node if config.exists(cfg_base + ['radius-server']): config.delete(cfg_base + ['radius-server']) - - try: - with open(file_name, 'w') as f: - f.write(config.to_string()) - except OSError as e: - print("Failed to save the modified config: {}".format(e)) - sys.exit(1) diff --git a/src/migration-scripts/l2tp/1-to-2 b/src/migration-scripts/l2tp/1-to-2 old mode 100755 new mode 100644 index 2ffb91c53..468d564ac --- a/src/migration-scripts/l2tp/1-to-2 +++ b/src/migration-scripts/l2tp/1-to-2 @@ -1,33 +1,28 @@ -#!/usr/bin/env python3 - -# Delete depricated outside-nexthop address - -import sys +# Copyright 2019-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# 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 <http://www.gnu.org/licenses/>. + +# T1858: Delete deprecated outside-nexthop from vyos.configtree import ConfigTree -if len(sys.argv) < 2: - print("Must specify file name!") - sys.exit(1) - -file_name = sys.argv[1] - -with open(file_name, 'r') as f: - config_file = f.read() +cfg_base = ['vpn', 'l2tp', 'remote-access'] -config = ConfigTree(config_file) +def migrate(config: ConfigTree) -> None: + if not config.exists(cfg_base): + # Nothing to do + return -cfg_base = ['vpn', 'l2tp', 'remote-access'] -if not config.exists(cfg_base): - # Nothing to do - sys.exit(0) -else: if config.exists(cfg_base + ['outside-nexthop']): config.delete(cfg_base + ['outside-nexthop']) - - try: - with open(file_name, 'w') as f: - f.write(config.to_string()) - except OSError as e: - print("Failed to save the modified config: {}".format(e)) - sys.exit(1) diff --git a/src/migration-scripts/l2tp/2-to-3 b/src/migration-scripts/l2tp/2-to-3 old mode 100755 new mode 100644 index 8527c2d4a..00fabb6b6 --- a/src/migration-scripts/l2tp/2-to-3 +++ b/src/migration-scripts/l2tp/2-to-3 @@ -1,107 +1,92 @@ -#!/usr/bin/env python3 +# Copyright 2020-2024 VyOS maintainers and contributors <maintainers@vyos.io> # -# Copyright (C) 2020-2024 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 program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, +# 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 General Public License for more details. +# 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 General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. -# - remove primary/secondary identifier from nameserver -# - TODO: remove radius server req-limit +# T2264: combine IPv4/IPv6 name-server CLI syntax +# T2264: combine WINS CLI syntax +# T2264: remove RADIUS req-limit node +# T2264: migrate IPv6 prefix node to common CLI style -from sys import argv, exit from vyos.configtree import ConfigTree -if len(argv) < 2: - print("Must specify file name!") - exit(1) - -file_name = argv[1] +base = ['vpn', 'l2tp', 'remote-access'] -with open(file_name, 'r') as f: - config_file = f.read() +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + # Nothing to do + return -config = ConfigTree(config_file) -base = ['vpn', 'l2tp', 'remote-access'] -if not config.exists(base): - # Nothing to do - exit(0) -else: # Migrate IPv4 DNS servers dns_base = base + ['dns-servers'] if config.exists(dns_base): for server in ['server-1', 'server-2']: if config.exists(dns_base + [server]): dns = config.return_value(dns_base + [server]) config.set(base + ['name-server'], value=dns, replace=False) config.delete(dns_base) # Migrate IPv6 DNS servers dns_base = base + ['dnsv6-servers'] if config.exists(dns_base): for server in config.return_values(dns_base): config.set(base + ['name-server'], value=server, replace=False) config.delete(dns_base) # Migrate IPv4 WINS servers wins_base = base + ['wins-servers'] if config.exists(wins_base): for server in ['server-1', 'server-2']: if config.exists(wins_base + [server]): wins = config.return_value(wins_base + [server]) config.set(base + ['wins-server'], value=wins, replace=False) config.delete(wins_base) # Remove RADIUS server req-limit node radius_base = base + ['authentication', 'radius'] if config.exists(radius_base): for server in config.list_nodes(radius_base + ['server']): if config.exists(radius_base + ['server', server, 'req-limit']): config.delete(radius_base + ['server', server, 'req-limit']) # Migrate IPv6 prefixes ipv6_base = base + ['client-ipv6-pool'] if config.exists(ipv6_base + ['prefix']): prefix_old = config.return_values(ipv6_base + ['prefix']) # delete old prefix CLI nodes config.delete(ipv6_base + ['prefix']) # create ned prefix tag node config.set(ipv6_base + ['prefix']) config.set_tag(ipv6_base + ['prefix']) for p in prefix_old: prefix = p.split(',')[0] mask = p.split(',')[1] config.set(ipv6_base + ['prefix', prefix, 'mask'], value=mask) if config.exists(ipv6_base + ['delegate-prefix']): prefix_old = config.return_values(ipv6_base + ['delegate-prefix']) # delete old delegate prefix CLI nodes config.delete(ipv6_base + ['delegate-prefix']) # create ned delegation tag node config.set(ipv6_base + ['delegate']) config.set_tag(ipv6_base + ['delegate']) for p in prefix_old: prefix = p.split(',')[0] mask = p.split(',')[1] config.set(ipv6_base + ['delegate', prefix, 'delegate-prefix'], value=mask) - - try: - with open(file_name, 'w') as f: - f.write(config.to_string()) - except OSError as e: - print("Failed to save the modified config: {}".format(e)) - exit(1) diff --git a/src/migration-scripts/l2tp/3-to-4 b/src/migration-scripts/l2tp/3-to-4 old mode 100755 new mode 100644 index 14b86ff04..01c3fa844 --- a/src/migration-scripts/l2tp/3-to-4 +++ b/src/migration-scripts/l2tp/3-to-4 @@ -1,168 +1,148 @@ -#!/usr/bin/env python3 +# Copyright 2021-2024 VyOS maintainers and contributors <maintainers@vyos.io> # -# Copyright (C) 2021-2024 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 program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, +# 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 General Public License for more details. +# 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 General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. -# - remove primary/secondary identifier from nameserver -# - TODO: remove radius server req-limit +# T2816: T3642: Move IPSec/L2TP code into vpn_ipsec.py and update to use PKI. import os -from sys import argv -from sys import exit from vyos.configtree import ConfigTree from vyos.pki import load_certificate from vyos.pki import load_private_key from vyos.pki import encode_certificate from vyos.pki import encode_private_key from vyos.utils.process import run -if len(argv) < 2: - print("Must specify file name!") - exit(1) - -file_name = argv[1] - -with open(file_name, 'r') as f: - config_file = f.read() - -config = ConfigTree(config_file) base = ['vpn', 'l2tp', 'remote-access', 'ipsec-settings'] pki_base = ['pki'] -if not config.exists(base): - exit(0) - AUTH_DIR = '/config/auth' def wrapped_pem_to_config_value(pem): return "".join(pem.strip().split("\n")[1:-1]) -if not config.exists(base + ['authentication', 'x509']): - exit(0) +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + return + + if not config.exists(base + ['authentication', 'x509']): + return + + x509_base = base + ['authentication', 'x509'] + pki_name = 'l2tp_remote_access' + + if not config.exists(pki_base + ['ca']): + config.set(pki_base + ['ca']) + config.set_tag(pki_base + ['ca']) + + if not config.exists(pki_base + ['certificate']): + config.set(pki_base + ['certificate']) + config.set_tag(pki_base + ['certificate']) + + if config.exists(x509_base + ['ca-cert-file']): + cert_file = config.return_value(x509_base + ['ca-cert-file']) + cert_path = os.path.join(AUTH_DIR, cert_file) + cert = None + + if os.path.isfile(cert_path): + if not os.access(cert_path, os.R_OK): + run(f'sudo chmod 644 {cert_path}') + + with open(cert_path, 'r') as f: + cert_data = f.read() + cert = load_certificate(cert_data, wrap_tags=False) + + if cert: + cert_pem = encode_certificate(cert) + config.set(pki_base + ['ca', pki_name, 'certificate'], value=wrapped_pem_to_config_value(cert_pem)) + config.set(x509_base + ['ca-certificate'], value=pki_name) + else: + print(f'Failed to migrate CA certificate on l2tp remote-access config') + + config.delete(x509_base + ['ca-cert-file']) + + if config.exists(x509_base + ['crl-file']): + crl_file = config.return_value(x509_base + ['crl-file']) + crl_path = os.path.join(AUTH_DIR, crl_file) + crl = None + + if os.path.isfile(crl_path): + if not os.access(crl_path, os.R_OK): + run(f'sudo chmod 644 {crl_path}') + + with open(crl_path, 'r') as f: + crl_data = f.read() + crl = load_certificate(crl_data, wrap_tags=False) + + if crl: + crl_pem = encode_certificate(crl) + config.set(pki_base + ['ca', pki_name, 'crl'], value=wrapped_pem_to_config_value(crl_pem)) + else: + print(f'Failed to migrate CRL on l2tp remote-access config') + + config.delete(x509_base + ['crl-file']) + + if config.exists(x509_base + ['server-cert-file']): + cert_file = config.return_value(x509_base + ['server-cert-file']) + cert_path = os.path.join(AUTH_DIR, cert_file) + cert = None + + if os.path.isfile(cert_path): + if not os.access(cert_path, os.R_OK): + run(f'sudo chmod 644 {cert_path}') + + with open(cert_path, 'r') as f: + cert_data = f.read() + cert = load_certificate(cert_data, wrap_tags=False) + + if cert: + cert_pem = encode_certificate(cert) + config.set(pki_base + ['certificate', pki_name, 'certificate'], value=wrapped_pem_to_config_value(cert_pem)) + config.set(x509_base + ['certificate'], value=pki_name) + else: + print(f'Failed to migrate certificate on l2tp remote-access config') + + config.delete(x509_base + ['server-cert-file']) + + if config.exists(x509_base + ['server-key-file']): + key_file = config.return_value(x509_base + ['server-key-file']) + key_passphrase = None -x509_base = base + ['authentication', 'x509'] -pki_name = 'l2tp_remote_access' + if config.exists(x509_base + ['server-key-password']): + key_passphrase = config.return_value(x509_base + ['server-key-password']) -if not config.exists(pki_base + ['ca']): - config.set(pki_base + ['ca']) - config.set_tag(pki_base + ['ca']) + key_path = os.path.join(AUTH_DIR, key_file) + key = None -if not config.exists(pki_base + ['certificate']): - config.set(pki_base + ['certificate']) - config.set_tag(pki_base + ['certificate']) + if os.path.isfile(key_path): + if not os.access(key_path, os.R_OK): + run(f'sudo chmod 644 {key_path}') -if config.exists(x509_base + ['ca-cert-file']): - cert_file = config.return_value(x509_base + ['ca-cert-file']) - cert_path = os.path.join(AUTH_DIR, cert_file) - cert = None - - if os.path.isfile(cert_path): - if not os.access(cert_path, os.R_OK): - run(f'sudo chmod 644 {cert_path}') - - with open(cert_path, 'r') as f: - cert_data = f.read() - cert = load_certificate(cert_data, wrap_tags=False) - - if cert: - cert_pem = encode_certificate(cert) - config.set(pki_base + ['ca', pki_name, 'certificate'], value=wrapped_pem_to_config_value(cert_pem)) - config.set(x509_base + ['ca-certificate'], value=pki_name) - else: - print(f'Failed to migrate CA certificate on l2tp remote-access config') - - config.delete(x509_base + ['ca-cert-file']) - -if config.exists(x509_base + ['crl-file']): - crl_file = config.return_value(x509_base + ['crl-file']) - crl_path = os.path.join(AUTH_DIR, crl_file) - crl = None - - if os.path.isfile(crl_path): - if not os.access(crl_path, os.R_OK): - run(f'sudo chmod 644 {crl_path}') - - with open(crl_path, 'r') as f: - crl_data = f.read() - crl = load_certificate(crl_data, wrap_tags=False) - - if crl: - crl_pem = encode_certificate(crl) - config.set(pki_base + ['ca', pki_name, 'crl'], value=wrapped_pem_to_config_value(crl_pem)) - else: - print(f'Failed to migrate CRL on l2tp remote-access config') - - config.delete(x509_base + ['crl-file']) - -if config.exists(x509_base + ['server-cert-file']): - cert_file = config.return_value(x509_base + ['server-cert-file']) - cert_path = os.path.join(AUTH_DIR, cert_file) - cert = None - - if os.path.isfile(cert_path): - if not os.access(cert_path, os.R_OK): - run(f'sudo chmod 644 {cert_path}') - - with open(cert_path, 'r') as f: - cert_data = f.read() - cert = load_certificate(cert_data, wrap_tags=False) - - if cert: - cert_pem = encode_certificate(cert) - config.set(pki_base + ['certificate', pki_name, 'certificate'], value=wrapped_pem_to_config_value(cert_pem)) - config.set(x509_base + ['certificate'], value=pki_name) - else: - print(f'Failed to migrate certificate on l2tp remote-access config') - - config.delete(x509_base + ['server-cert-file']) - -if config.exists(x509_base + ['server-key-file']): - key_file = config.return_value(x509_base + ['server-key-file']) - key_passphrase = None - - if config.exists(x509_base + ['server-key-password']): - key_passphrase = config.return_value(x509_base + ['server-key-password']) - - key_path = os.path.join(AUTH_DIR, key_file) - key = None - - if os.path.isfile(key_path): - if not os.access(key_path, os.R_OK): - run(f'sudo chmod 644 {key_path}') - - with open(key_path, 'r') as f: - key_data = f.read() - key = load_private_key(key_data, passphrase=key_passphrase, wrap_tags=False) - - if key: - key_pem = encode_private_key(key, passphrase=key_passphrase) - config.set(pki_base + ['certificate', pki_name, 'private', 'key'], value=wrapped_pem_to_config_value(key_pem)) - - if key_passphrase: - config.set(pki_base + ['certificate', pki_name, 'private', 'password-protected']) - config.set(x509_base + ['private-key-passphrase'], value=key_passphrase) - else: - print(f'Failed to migrate private key on l2tp remote-access config') - - config.delete(x509_base + ['server-key-file']) - if config.exists(x509_base + ['server-key-password']): - config.delete(x509_base + ['server-key-password']) - -try: - with open(file_name, 'w') as f: - f.write(config.to_string()) -except OSError as e: - print("Failed to save the modified config: {}".format(e)) - exit(1) + with open(key_path, 'r') as f: + key_data = f.read() + key = load_private_key(key_data, passphrase=key_passphrase, wrap_tags=False) + + if key: + key_pem = encode_private_key(key, passphrase=key_passphrase) + config.set(pki_base + ['certificate', pki_name, 'private', 'key'], value=wrapped_pem_to_config_value(key_pem)) + + if key_passphrase: + config.set(pki_base + ['certificate', pki_name, 'private', 'password-protected']) + config.set(x509_base + ['private-key-passphrase'], value=key_passphrase) + else: + print(f'Failed to migrate private key on l2tp remote-access config') + + config.delete(x509_base + ['server-key-file']) + if config.exists(x509_base + ['server-key-password']): + config.delete(x509_base + ['server-key-password']) diff --git a/src/migration-scripts/l2tp/4-to-5 b/src/migration-scripts/l2tp/4-to-5 old mode 100755 new mode 100644 index b7f4d2677..56d451b8d --- a/src/migration-scripts/l2tp/4-to-5 +++ b/src/migration-scripts/l2tp/4-to-5 @@ -1,85 +1,68 @@ -#!/usr/bin/env python3 +# Copyright 2023-2024 VyOS maintainers and contributors <maintainers@vyos.io> # -# Copyright (C) 2023-2024 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 program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, +# 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 General Public License for more details. +# 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 General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. # - move all pool to named pools # 'start-stop' migrate to namedpool 'default-range-pool' # 'subnet' migrate to namedpool 'default-subnet-pool' # 'default-subnet-pool' is the next pool for 'default-range-pool' -from sys import argv -from sys import exit from vyos.configtree import ConfigTree from vyos.base import Warning -if len(argv) < 2: - print("Must specify file name!") - exit(1) - -file_name = argv[1] - -with open(file_name, 'r') as f: - config_file = f.read() - -config = ConfigTree(config_file) base = ['vpn', 'l2tp', 'remote-access'] pool_base = base + ['client-ip-pool'] -if not config.exists(base): - exit(0) -if not config.exists(pool_base): - exit(0) -default_pool = '' -range_pool_name = 'default-range-pool' +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + return -if config.exists(pool_base + ['start']) and config.exists(pool_base + ['stop']): - def is_legalrange(ip1: str, ip2: str, mask: str): - from ipaddress import IPv4Interface - interface1 = IPv4Interface(f'{ip1}/{mask}') + if not config.exists(pool_base): + return - interface2 = IPv4Interface(f'{ip2}/{mask}') - return interface1.network.network_address == interface2.network.network_address and interface2.ip > interface1.ip + default_pool = '' + range_pool_name = 'default-range-pool' - start_ip = config.return_value(pool_base + ['start']) - stop_ip = config.return_value(pool_base + ['stop']) - if is_legalrange(start_ip, stop_ip,'24'): - ip_range = f'{start_ip}-{stop_ip}' - config.set(pool_base + [range_pool_name, 'range'], value=ip_range, replace=False) - default_pool = range_pool_name - else: - Warning( - f'L2TP client-ip-pool range start-ip:{start_ip} and stop-ip:{stop_ip} can not be migrated.') + if config.exists(pool_base + ['start']) and config.exists(pool_base + ['stop']): + def is_legalrange(ip1: str, ip2: str, mask: str): + from ipaddress import IPv4Interface + interface1 = IPv4Interface(f'{ip1}/{mask}') + + interface2 = IPv4Interface(f'{ip2}/{mask}') + return interface1.network.network_address == interface2.network.network_address and interface2.ip > interface1.ip - config.delete(pool_base + ['start']) - config.delete(pool_base + ['stop']) + start_ip = config.return_value(pool_base + ['start']) + stop_ip = config.return_value(pool_base + ['stop']) + if is_legalrange(start_ip, stop_ip,'24'): + ip_range = f'{start_ip}-{stop_ip}' + config.set(pool_base + [range_pool_name, 'range'], value=ip_range, replace=False) + default_pool = range_pool_name + else: + Warning( + f'L2TP client-ip-pool range start-ip:{start_ip} and stop-ip:{stop_ip} can not be migrated.') -if config.exists(pool_base + ['subnet']): - for subnet in config.return_values(pool_base + ['subnet']): - config.set(pool_base + [range_pool_name, 'range'], value=subnet, replace=False) + config.delete(pool_base + ['start']) + config.delete(pool_base + ['stop']) - config.delete(pool_base + ['subnet']) - default_pool = range_pool_name + if config.exists(pool_base + ['subnet']): + for subnet in config.return_values(pool_base + ['subnet']): + config.set(pool_base + [range_pool_name, 'range'], value=subnet, replace=False) -if default_pool: - config.set(base + ['default-pool'], value=default_pool) -# format as tag node -config.set_tag(pool_base) + config.delete(pool_base + ['subnet']) + default_pool = range_pool_name -try: - with open(file_name, 'w') as f: - f.write(config.to_string()) -except OSError as e: - print("Failed to save the modified config: {}".format(e)) - exit(1) + if default_pool: + config.set(base + ['default-pool'], value=default_pool) + # format as tag node + config.set_tag(pool_base) diff --git a/src/migration-scripts/l2tp/5-to-6 b/src/migration-scripts/l2tp/5-to-6 old mode 100755 new mode 100644 index ac40b89c8..cc9f948a6 --- a/src/migration-scripts/l2tp/5-to-6 +++ b/src/migration-scripts/l2tp/5-to-6 @@ -1,106 +1,88 @@ -#!/usr/bin/env python3 +# Copyright 2023-2024 VyOS maintainers and contributors <maintainers@vyos.io> # -# Copyright (C) 2024 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 program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, +# 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 General Public License for more details. +# 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 General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. -from sys import argv -from sys import exit from vyos.configtree import ConfigTree -if len(argv) < 2: - print("Must specify file name!") - exit(1) - -file_name = argv[1] - -with open(file_name, 'r') as f: - config_file = f.read() - -config = ConfigTree(config_file) base = ['vpn', 'l2tp', 'remote-access'] -if not config.exists(base): - exit(0) - -#migrate idle to ppp option lcp-echo-timeout -idle_path = base + ['idle'] -if config.exists(idle_path): - config.set(base + ['ppp-options', 'lcp-echo-timeout'], - value=config.return_value(idle_path)) - config.delete(idle_path) - -#migrate mppe from authentication to ppp-otion -mppe_path = base + ['authentication', 'mppe'] -if config.exists(mppe_path): - config.set(base + ['ppp-options', 'mppe'], - value=config.return_value(mppe_path)) - config.delete(mppe_path) - -#migrate require to protocol -require_path = base + ['authentication', 'require'] -if config.exists(require_path): - protocols = list(config.return_values(require_path)) - for protocol in protocols: - config.set(base + ['authentication', 'protocols'], value=protocol, - replace=False) - config.delete(require_path) -else: - config.set(base + ['authentication', 'protocols'], value='mschap-v2') - -#migrate default gateway if not exist -if not config.exists(base + ['gateway-address']): - config.set(base + ['gateway-address'], value='10.255.255.0') - -#migrate authentication radius timeout -rad_timeout_path = base + ['authentication', 'radius', 'timeout'] -if config.exists(rad_timeout_path): - if int(config.return_value(rad_timeout_path)) > 60: - config.set(rad_timeout_path, value=60) - -#migrate authentication radius acct timeout -rad_acct_timeout_path = base + ['authentication', 'radius', 'acct-timeout'] -if config.exists(rad_acct_timeout_path): - if int(config.return_value(rad_acct_timeout_path)) > 60: - config.set(rad_acct_timeout_path,value=60) - -#migrate authentication radius max-try -rad_max_try_path = base + ['authentication', 'radius', 'max-try'] -if config.exists(rad_max_try_path): - if int(config.return_value(rad_max_try_path)) > 20: - config.set(rad_max_try_path, value=20) - -#migrate dae-server to dynamic-author -dae_path_old = base + ['authentication', 'radius', 'dae-server'] -dae_path_new = base + ['authentication', 'radius', 'dynamic-author'] - -if config.exists(dae_path_old + ['ip-address']): - config.set(dae_path_new + ['server'], - value=config.return_value(dae_path_old + ['ip-address'])) - -if config.exists(dae_path_old + ['port']): - config.set(dae_path_new + ['port'], - value=config.return_value(dae_path_old + ['port'])) - -if config.exists(dae_path_old + ['secret']): - config.set(dae_path_new + ['key'], - value=config.return_value(dae_path_old + ['secret'])) - -if config.exists(dae_path_old): - config.delete(dae_path_old) -try: - with open(file_name, 'w') as f: - f.write(config.to_string()) -except OSError as e: - print("Failed to save the modified config: {}".format(e)) - exit(1) +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + return + + #migrate idle to ppp option lcp-echo-timeout + idle_path = base + ['idle'] + if config.exists(idle_path): + config.set(base + ['ppp-options', 'lcp-echo-timeout'], + value=config.return_value(idle_path)) + config.delete(idle_path) + + #migrate mppe from authentication to ppp-otion + mppe_path = base + ['authentication', 'mppe'] + if config.exists(mppe_path): + config.set(base + ['ppp-options', 'mppe'], + value=config.return_value(mppe_path)) + config.delete(mppe_path) + + #migrate require to protocol + require_path = base + ['authentication', 'require'] + if config.exists(require_path): + protocols = list(config.return_values(require_path)) + for protocol in protocols: + config.set(base + ['authentication', 'protocols'], value=protocol, + replace=False) + config.delete(require_path) + else: + config.set(base + ['authentication', 'protocols'], value='mschap-v2') + + #migrate default gateway if not exist + if not config.exists(base + ['gateway-address']): + config.set(base + ['gateway-address'], value='10.255.255.0') + + #migrate authentication radius timeout + rad_timeout_path = base + ['authentication', 'radius', 'timeout'] + if config.exists(rad_timeout_path): + if int(config.return_value(rad_timeout_path)) > 60: + config.set(rad_timeout_path, value=60) + + #migrate authentication radius acct timeout + rad_acct_timeout_path = base + ['authentication', 'radius', 'acct-timeout'] + if config.exists(rad_acct_timeout_path): + if int(config.return_value(rad_acct_timeout_path)) > 60: + config.set(rad_acct_timeout_path,value=60) + + #migrate authentication radius max-try + rad_max_try_path = base + ['authentication', 'radius', 'max-try'] + if config.exists(rad_max_try_path): + if int(config.return_value(rad_max_try_path)) > 20: + config.set(rad_max_try_path, value=20) + + #migrate dae-server to dynamic-author + dae_path_old = base + ['authentication', 'radius', 'dae-server'] + dae_path_new = base + ['authentication', 'radius', 'dynamic-author'] + + if config.exists(dae_path_old + ['ip-address']): + config.set(dae_path_new + ['server'], + value=config.return_value(dae_path_old + ['ip-address'])) + + if config.exists(dae_path_old + ['port']): + config.set(dae_path_new + ['port'], + value=config.return_value(dae_path_old + ['port'])) + + if config.exists(dae_path_old + ['secret']): + config.set(dae_path_new + ['key'], + value=config.return_value(dae_path_old + ['secret'])) + + if config.exists(dae_path_old): + config.delete(dae_path_old) diff --git a/src/migration-scripts/l2tp/6-to-7 b/src/migration-scripts/l2tp/6-to-7 old mode 100755 new mode 100644 index 1c536585c..4dba5974e --- a/src/migration-scripts/l2tp/6-to-7 +++ b/src/migration-scripts/l2tp/6-to-7 @@ -1,57 +1,39 @@ -#!/usr/bin/env python3 +# Copyright 2024 VyOS maintainers and contributors <maintainers@vyos.io> # -# Copyright (C) 2024 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 program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, +# 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 General Public License for more details. +# 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 General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. # Migrating to named ipv6 pools -from sys import argv -from sys import exit from vyos.configtree import ConfigTree -if len(argv) < 2: - print("Must specify file name!") - exit(1) - -file_name = argv[1] - -with open(file_name, 'r') as f: - config_file = f.read() - -config = ConfigTree(config_file) base = ['vpn', 'l2tp', 'remote-access'] pool_base = base + ['client-ipv6-pool'] -if not config.exists(base): - exit(0) -if not config.exists(pool_base): - exit(0) +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + return -ipv6_pool_name = 'ipv6-pool' -config.copy(pool_base, pool_base + [ipv6_pool_name]) + if not config.exists(pool_base): + return -if config.exists(pool_base + ['prefix']): - config.delete(pool_base + ['prefix']) - config.set(base + ['default-ipv6-pool'], value=ipv6_pool_name) -if config.exists(pool_base + ['delegate']): - config.delete(pool_base + ['delegate']) -# format as tag node -config.set_tag(pool_base) + ipv6_pool_name = 'ipv6-pool' + config.copy(pool_base, pool_base + [ipv6_pool_name]) -try: - with open(file_name, 'w') as f: - f.write(config.to_string()) -except OSError as e: - print("Failed to save the modified config: {}".format(e)) - exit(1) + if config.exists(pool_base + ['prefix']): + config.delete(pool_base + ['prefix']) + config.set(base + ['default-ipv6-pool'], value=ipv6_pool_name) + if config.exists(pool_base + ['delegate']): + config.delete(pool_base + ['delegate']) + # format as tag node + config.set_tag(pool_base) diff --git a/src/migration-scripts/l2tp/7-to-8 b/src/migration-scripts/l2tp/7-to-8 old mode 100755 new mode 100644 index e429ed057..527906fc8 --- a/src/migration-scripts/l2tp/7-to-8 +++ b/src/migration-scripts/l2tp/7-to-8 @@ -1,65 +1,47 @@ -#!/usr/bin/env python3 +# Copyright 2024 VyOS maintainers and contributors <maintainers@vyos.io> # -# Copyright (C) 2024 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 program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, +# 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 General Public License for more details. +# 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 General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. # Migrate from 'ccp-disable' to 'ppp-options.disable-ccp' # Migration ipv6 options -from sys import argv -from sys import exit from vyos.configtree import ConfigTree -if len(argv) < 2: - print("Must specify file name!") - exit(1) - -file_name = argv[1] - -with open(file_name, 'r') as f: - config_file = f.read() - -config = ConfigTree(config_file) base = ['vpn', 'l2tp', 'remote-access'] -if not config.exists(base): - exit(0) - -#CCP migration -if config.exists(base + ['ccp-disable']): - config.delete(base + ['ccp-disable']) - config.set(base + ['ppp-options', 'disable-ccp']) - -#IPV6 options migrations -if config.exists(base + ['ppp-options','ipv6-peer-intf-id']): - intf_peer_id = config.return_value(base + ['ppp-options','ipv6-peer-intf-id']) - if intf_peer_id == 'ipv4': - intf_peer_id = 'ipv4-addr' - config.set(base + ['ppp-options','ipv6-peer-interface-id'], value=intf_peer_id, replace=True) - config.delete(base + ['ppp-options','ipv6-peer-intf-id']) - -if config.exists(base + ['ppp-options','ipv6-intf-id']): - intf_id = config.return_value(base + ['ppp-options','ipv6-intf-id']) - config.set(base + ['ppp-options','ipv6-interface-id'], value=intf_id, replace=True) - config.delete(base + ['ppp-options','ipv6-intf-id']) - -if config.exists(base + ['ppp-options','ipv6-accept-peer-intf-id']): - config.set(base + ['ppp-options','ipv6-accept-peer-interface-id']) - config.delete(base + ['ppp-options','ipv6-accept-peer-intf-id']) -try: - with open(file_name, 'w') as f: - f.write(config.to_string()) -except OSError as e: - print("Failed to save the modified config: {}".format(e)) - exit(1) +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + return + + #CCP migration + if config.exists(base + ['ccp-disable']): + config.delete(base + ['ccp-disable']) + config.set(base + ['ppp-options', 'disable-ccp']) + + #IPV6 options migrations + if config.exists(base + ['ppp-options','ipv6-peer-intf-id']): + intf_peer_id = config.return_value(base + ['ppp-options','ipv6-peer-intf-id']) + if intf_peer_id == 'ipv4': + intf_peer_id = 'ipv4-addr' + config.set(base + ['ppp-options','ipv6-peer-interface-id'], value=intf_peer_id, replace=True) + config.delete(base + ['ppp-options','ipv6-peer-intf-id']) + + if config.exists(base + ['ppp-options','ipv6-intf-id']): + intf_id = config.return_value(base + ['ppp-options','ipv6-intf-id']) + config.set(base + ['ppp-options','ipv6-interface-id'], value=intf_id, replace=True) + config.delete(base + ['ppp-options','ipv6-intf-id']) + + if config.exists(base + ['ppp-options','ipv6-accept-peer-intf-id']): + config.set(base + ['ppp-options','ipv6-accept-peer-interface-id']) + config.delete(base + ['ppp-options','ipv6-accept-peer-intf-id']) diff --git a/src/migration-scripts/l2tp/8-to-9 b/src/migration-scripts/l2tp/8-to-9 old mode 100755 new mode 100644 index 672180e25..e6b689e80 --- a/src/migration-scripts/l2tp/8-to-9 +++ b/src/migration-scripts/l2tp/8-to-9 @@ -1,46 +1,28 @@ -#!/usr/bin/env python3 +# Copyright 2024 VyOS maintainers and contributors <maintainers@vyos.io> # -# Copyright (C) 2024 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 program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, +# 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 General Public License for more details. +# 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 General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. # Deleted 'dhcp-interface' from l2tp -from sys import argv -from sys import exit from vyos.configtree import ConfigTree -if len(argv) < 2: - print("Must specify file name!") - exit(1) - -file_name = argv[1] - -with open(file_name, 'r') as f: - config_file = f.read() - -config = ConfigTree(config_file) base = ['vpn', 'l2tp', 'remote-access'] -if not config.exists(base): - exit(0) -# deleting unused dhcp-interface -if config.exists(base + ['dhcp-interface']): - config.delete(base + ['dhcp-interface']) +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + return -try: - with open(file_name, 'w') as f: - f.write(config.to_string()) -except OSError as e: - print("Failed to save the modified config: {}".format(e)) - exit(1) + # deleting unused dhcp-interface + if config.exists(base + ['dhcp-interface']): + config.delete(base + ['dhcp-interface']) diff --git a/src/migration-scripts/lldp/0-to-1 b/src/migration-scripts/lldp/0-to-1 old mode 100755 new mode 100644 index a99356062..c16e7e84b --- a/src/migration-scripts/lldp/0-to-1 +++ b/src/migration-scripts/lldp/0-to-1 @@ -1,49 +1,31 @@ -#!/usr/bin/env python3 +# Copyright 2020-2024 VyOS maintainers and contributors <maintainers@vyos.io> # -# Copyright (C) 2020 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 program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, +# 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 General Public License for more details. +# 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 General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. # Delete "set service lldp interface <interface> location civic-based" option # as it was broken most of the time anyways -import sys - from vyos.configtree import ConfigTree -if len(sys.argv) < 2: - print("Must specify file name!") - sys.exit(1) - -file_name = sys.argv[1] +base = ['service', 'lldp', 'interface'] -with open(file_name, 'r') as f: - config_file = f.read() +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + # Nothing to do + return -config = ConfigTree(config_file) -base = ['service', 'lldp', 'interface'] -if not config.exists(base): - # Nothing to do - sys.exit(0) -else: # Delete nodes with abandoned CLI syntax for interface in config.list_nodes(base): if config.exists(base + [interface, 'location', 'civic-based']): config.delete(base + [interface, 'location', 'civic-based']) - - try: - with open(file_name, 'w') as f: - f.write(config.to_string()) - except OSError as e: - print("Failed to save the modified config: {}".format(e)) - sys.exit(1) diff --git a/src/migration-scripts/lldp/1-to-2 b/src/migration-scripts/lldp/1-to-2 old mode 100755 new mode 100644 index 35efb25db..7f233a725 --- a/src/migration-scripts/lldp/1-to-2 +++ b/src/migration-scripts/lldp/1-to-2 @@ -1,48 +1,30 @@ -#!/usr/bin/env python3 +# Copyright 2023-2024 VyOS maintainers and contributors <maintainers@vyos.io> # -# Copyright (C) 2023 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 program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, +# 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 General Public License for more details. +# 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 General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. # T5855: migrate "set service lldp snmp enable" -> `set service lldp snmp" -import sys - from vyos.configtree import ConfigTree -if len(sys.argv) < 2: - print("Must specify file name!") - sys.exit(1) - -file_name = sys.argv[1] - -with open(file_name, 'r') as f: - config_file = f.read() - -config = ConfigTree(config_file) base = ['service', 'lldp'] -if not config.exists(base): - # Nothing to do - sys.exit(0) -if config.exists(base + ['snmp']): - enabled = config.exists(base + ['snmp', 'enable']) - config.delete(base + ['snmp']) - if enabled: config.set(base + ['snmp']) +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + # Nothing to do + return -try: - with open(file_name, 'w') as f: - f.write(config.to_string()) -except OSError as e: - print("Failed to save the modified config: {}".format(e)) - sys.exit(1) + if config.exists(base + ['snmp']): + enabled = config.exists(base + ['snmp', 'enable']) + config.delete(base + ['snmp']) + if enabled: config.set(base + ['snmp']) diff --git a/src/migration-scripts/monitoring/0-to-1 b/src/migration-scripts/monitoring/0-to-1 old mode 100755 new mode 100644 index 384d22f8c..92f824325 --- a/src/migration-scripts/monitoring/0-to-1 +++ b/src/migration-scripts/monitoring/0-to-1 @@ -1,71 +1,66 @@ #!/usr/bin/env python3 # # Copyright (C) 2022 VyOS maintainers and contributors # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License version 2 or later as # published by the Free Software Foundation. # # This program 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 General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. -# T3417: migrate IS-IS tagNode to node as we can only have one IS-IS process +# Copyright 2022-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# 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 <http://www.gnu.org/licenses/>. -from sys import argv -from sys import exit +# T3417: migrate IS-IS tagNode to node as we can only have one IS-IS process from vyos.configtree import ConfigTree -if len(argv) < 2: - print("Must specify file name!") - exit(1) - -file_name = argv[1] - -with open(file_name, 'r') as f: - config_file = f.read() - base = ['service', 'monitoring', 'telegraf'] -config = ConfigTree(config_file) - -if not config.exists(base): - # Nothing to do - exit(0) - -if config.exists(base + ['authentication', 'organization']): - tmp = config.return_value(base + ['authentication', 'organization']) - config.delete(base + ['authentication', 'organization']) - config.set(base + ['influxdb', 'authentication', 'organization'], value=tmp) -if config.exists(base + ['authentication', 'token']): - tmp = config.return_value(base + ['authentication', 'token']) - config.delete(base + ['authentication', 'token']) - config.set(base + ['influxdb', 'authentication', 'token'], value=tmp) +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + # Nothing to do + return -if config.exists(base + ['bucket']): - tmp = config.return_value(base + ['bucket']) - config.delete(base + ['bucket']) - config.set(base + ['influxdb', 'bucket'], value=tmp) + if config.exists(base + ['authentication', 'organization']): + tmp = config.return_value(base + ['authentication', 'organization']) + config.delete(base + ['authentication', 'organization']) + config.set(base + ['influxdb', 'authentication', 'organization'], value=tmp) -if config.exists(base + ['port']): - tmp = config.return_value(base + ['port']) - config.delete(base + ['port']) - config.set(base + ['influxdb', 'port'], value=tmp) + if config.exists(base + ['authentication', 'token']): + tmp = config.return_value(base + ['authentication', 'token']) + config.delete(base + ['authentication', 'token']) + config.set(base + ['influxdb', 'authentication', 'token'], value=tmp) -if config.exists(base + ['url']): - tmp = config.return_value(base + ['url']) - config.delete(base + ['url']) - config.set(base + ['influxdb', 'url'], value=tmp) + if config.exists(base + ['bucket']): + tmp = config.return_value(base + ['bucket']) + config.delete(base + ['bucket']) + config.set(base + ['influxdb', 'bucket'], value=tmp) + if config.exists(base + ['port']): + tmp = config.return_value(base + ['port']) + config.delete(base + ['port']) + config.set(base + ['influxdb', 'port'], value=tmp) -try: - with open(file_name, 'w') as f: - f.write(config.to_string()) -except OSError as e: - print(f'Failed to save the modified config: {e}') - exit(1) + if config.exists(base + ['url']): + tmp = config.return_value(base + ['url']) + config.delete(base + ['url']) + config.set(base + ['influxdb', 'url'], value=tmp) diff --git a/src/migration-scripts/nat/4-to-5 b/src/migration-scripts/nat/4-to-5 old mode 100755 new mode 100644 index ce215d455..e1919da50 --- a/src/migration-scripts/nat/4-to-5 +++ b/src/migration-scripts/nat/4-to-5 @@ -1,64 +1,45 @@ -#!/usr/bin/env python3 +# Copyright 2020-2024 VyOS maintainers and contributors <maintainers@vyos.io> # -# Copyright (C) 2020 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 program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, +# 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 General Public License for more details. +# 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 General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. # Drop the enable/disable from the nat "log" node. If log node is specified # it is "enabled" -from sys import argv,exit from vyos.configtree import ConfigTree -if len(argv) < 2: - print("Must specify file name!") - exit(1) - -file_name = argv[1] - -with open(file_name, 'r') as f: - config_file = f.read() +def migrate(config: ConfigTree) -> None: + if not config.exists(['nat']): + # Nothing to do + return -config = ConfigTree(config_file) - -if not config.exists(['nat']): - # Nothing to do - exit(0) -else: for direction in ['source', 'destination']: # If a node doesn't exist, we obviously have nothing to do. if not config.exists(['nat', direction]): continue # However, we also need to handle the case when a 'source' or 'destination' sub-node does exist, # but there are no rules under it. if not config.list_nodes(['nat', direction]): continue for rule in config.list_nodes(['nat', direction, 'rule']): base = ['nat', direction, 'rule', rule] # Check if the log node exists and if log is enabled, # migrate it to the new valueless 'log' node if config.exists(base + ['log']): tmp = config.return_value(base + ['log']) config.delete(base + ['log']) if tmp == 'enable': config.set(base + ['log']) - - try: - with open(file_name, 'w') as f: - f.write(config.to_string()) - except OSError as e: - print("Failed to save the modified config: {}".format(e)) - exit(1) diff --git a/src/migration-scripts/nat/5-to-6 b/src/migration-scripts/nat/5-to-6 old mode 100755 new mode 100644 index cfe98ddcf..a583d4eb6 --- a/src/migration-scripts/nat/5-to-6 +++ b/src/migration-scripts/nat/5-to-6 @@ -1,101 +1,82 @@ -#!/usr/bin/env python3 +# Copyright 2023-2024 VyOS maintainers and contributors <maintainers@vyos.io> # -# Copyright (C) 2024 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 program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, +# 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 General Public License for more details. +# 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 General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. # T5643: move from 'set nat [source|destination] rule X [inbound-interface|outbound interface] <iface>' # to # 'set nat [source|destination] rule X [inbound-interface|outbound interface] interface-name <iface>' # T6100: Migration from 1.3.X to 1.4 # Change IP/netmask to Network/netmask in # 'set nat [source|destination] rule X [source| destination| translation] address <IP/Netmask| !IP/Netmask>' import ipaddress -from sys import argv,exit + from vyos.configtree import ConfigTree def _func_T5643(conf, base_path): for iface in ['inbound-interface', 'outbound-interface']: if conf.exists(base_path + [iface]): tmp = conf.return_value(base_path + [iface]) if tmp: conf.delete(base_path + [iface]) conf.set(base_path + [iface, 'interface-name'], value=tmp) return def _func_T6100(conf, base_path): for addr_type in ['source', 'destination', 'translation']: base_addr_type = base_path + [addr_type] if not conf.exists(base_addr_type) or not conf.exists( base_addr_type + ['address']): continue address = conf.return_value(base_addr_type + ['address']) if not address or '/' not in address: continue negative = '' network = address if '!' in address: negative = '!' network = str(address.split(negative)[1]) network_ip = ipaddress.ip_network(network, strict=False) if str(network_ip) != network: network = f'{negative}{str(network_ip)}' conf.set(base_addr_type + ['address'], value=network) return -if __name__ == '__main__': - if len(argv) < 2: - print("Must specify file name!") - exit(1) - - file_name = argv[1] - - with open(file_name, 'r') as f: - config_file = f.read() - - config = ConfigTree(config_file) - +def migrate(config: ConfigTree) -> None: if not config.exists(['nat']): # Nothing to do - exit(0) + return for direction in ['source', 'destination']: # If a node doesn't exist, we obviously have nothing to do. if not config.exists(['nat', direction]): continue # However, we also need to handle the case when a 'source' or 'destination' sub-node does exist, # but there are no rules under it. if not config.list_nodes(['nat', direction]): continue for rule in config.list_nodes(['nat', direction, 'rule']): base = ['nat', direction, 'rule', rule] _func_T5643(config,base) _func_T6100(config,base) - - try: - with open(file_name, 'w') as f: - f.write(config.to_string()) - except OSError as e: - print("Failed to save the modified config: {}".format(e)) - exit(1) diff --git a/src/migration-scripts/nat/6-to-7 b/src/migration-scripts/nat/6-to-7 old mode 100755 new mode 100644 index 25640dec2..e9b90fc98 --- a/src/migration-scripts/nat/6-to-7 +++ b/src/migration-scripts/nat/6-to-7 @@ -1,73 +1,54 @@ -#!/usr/bin/env python3 +# Copyright 2023-2024 VyOS maintainers and contributors <maintainers@vyos.io> # -# Copyright (C) 2023 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 program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, +# 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 General Public License for more details. +# 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 General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. # T5681: Firewall re-writing. Simplify cli when mathcing interface # From # 'set nat [source|destination] rule X [inbound-interface|outbound interface] interface-name <iface>' # 'set nat [source|destination] rule X [inbound-interface|outbound interface] interface-group <iface_group>' # to # 'set nat [source|destination] rule X [inbound-interface|outbound interface] name <iface>' # 'set nat [source|destination] rule X [inbound-interface|outbound interface] group <iface_group>' # Also remove command if interface == any -from sys import argv,exit from vyos.configtree import ConfigTree -if len(argv) < 2: - print("Must specify file name!") - exit(1) - -file_name = argv[1] - -with open(file_name, 'r') as f: - config_file = f.read() - -config = ConfigTree(config_file) - -if not config.exists(['nat']): - # Nothing to do - exit(0) - -for direction in ['source', 'destination']: - # If a node doesn't exist, we obviously have nothing to do. - if not config.exists(['nat', direction]): - continue - - # However, we also need to handle the case when a 'source' or 'destination' sub-node does exist, - # but there are no rules under it. - if not config.list_nodes(['nat', direction]): - continue - - for rule in config.list_nodes(['nat', direction, 'rule']): - base = ['nat', direction, 'rule', rule] - for iface in ['inbound-interface','outbound-interface']: - if config.exists(base + [iface]): - if config.exists(base + [iface, 'interface-name']): - tmp = config.return_value(base + [iface, 'interface-name']) - if tmp != 'any': - config.delete(base + [iface, 'interface-name']) - if '+' in tmp: - tmp = tmp.replace('+', '*') - config.set(base + [iface, 'name'], value=tmp) - else: - config.delete(base + [iface]) - -try: - with open(file_name, 'w') as f: - f.write(config.to_string()) -except OSError as e: - print("Failed to save the modified config: {}".format(e)) - exit(1) +def migrate(config: ConfigTree) -> None: + if not config.exists(['nat']): + # Nothing to do + return + + for direction in ['source', 'destination']: + # If a node doesn't exist, we obviously have nothing to do. + if not config.exists(['nat', direction]): + continue + + # However, we also need to handle the case when a 'source' or 'destination' sub-node does exist, + # but there are no rules under it. + if not config.list_nodes(['nat', direction]): + continue + + for rule in config.list_nodes(['nat', direction, 'rule']): + base = ['nat', direction, 'rule', rule] + for iface in ['inbound-interface','outbound-interface']: + if config.exists(base + [iface]): + if config.exists(base + [iface, 'interface-name']): + tmp = config.return_value(base + [iface, 'interface-name']) + if tmp != 'any': + config.delete(base + [iface, 'interface-name']) + if '+' in tmp: + tmp = tmp.replace('+', '*') + config.set(base + [iface, 'name'], value=tmp) + else: + config.delete(base + [iface]) diff --git a/src/migration-scripts/nat/7-to-8 b/src/migration-scripts/nat/7-to-8 old mode 100755 new mode 100644 index ab2ffa6d3..9ae389ef1 --- a/src/migration-scripts/nat/7-to-8 +++ b/src/migration-scripts/nat/7-to-8 @@ -1,62 +1,43 @@ -#!/usr/bin/env python3 +# Copyright 2024 VyOS maintainers and contributors <maintainers@vyos.io> # -# Copyright (C) 2024 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 program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, +# 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 General Public License for more details. +# 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 General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. # T6345: random - In kernel 5.0 and newer this is the same as fully-random. # In earlier kernels the port mapping will be randomized using a seeded # MD5 hash mix using source and destination address and destination port. # drop fully-random from CLI -from sys import argv,exit from vyos.configtree import ConfigTree -if len(argv) < 2: - print("Must specify file name!") - exit(1) - -file_name = argv[1] - -with open(file_name, 'r') as f: - config_file = f.read() - -config = ConfigTree(config_file) - -if not config.exists(['nat']): - # Nothing to do - exit(0) - -for direction in ['source', 'destination']: - # If a node doesn't exist, we obviously have nothing to do. - if not config.exists(['nat', direction]): - continue - - # However, we also need to handle the case when a 'source' or 'destination' sub-node does exist, - # but there are no rules under it. - if not config.list_nodes(['nat', direction]): - continue - - for rule in config.list_nodes(['nat', direction, 'rule']): - port_mapping = ['nat', direction, 'rule', rule, 'translation', 'options', 'port-mapping'] - if config.exists(port_mapping): - tmp = config.return_value(port_mapping) - if tmp == 'fully-random': - config.set(port_mapping, value='random') - -try: - with open(file_name, 'w') as f: - f.write(config.to_string()) -except OSError as e: - print(f'Failed to save the modified config: {e}') - exit(1) +def migrate(config: ConfigTree) -> None: + if not config.exists(['nat']): + # Nothing to do + return + + for direction in ['source', 'destination']: + # If a node doesn't exist, we obviously have nothing to do. + if not config.exists(['nat', direction]): + continue + + # However, we also need to handle the case when a 'source' or 'destination' sub-node does exist, + # but there are no rules under it. + if not config.list_nodes(['nat', direction]): + continue + + for rule in config.list_nodes(['nat', direction, 'rule']): + port_mapping = ['nat', direction, 'rule', rule, 'translation', 'options', 'port-mapping'] + if config.exists(port_mapping): + tmp = config.return_value(port_mapping) + if tmp == 'fully-random': + config.set(port_mapping, value='random') diff --git a/src/migration-scripts/nat66/0-to-1 b/src/migration-scripts/nat66/0-to-1 old mode 100755 new mode 100644 index 444b2315f..b3c6bf4cc --- a/src/migration-scripts/nat66/0-to-1 +++ b/src/migration-scripts/nat66/0-to-1 @@ -1,71 +1,52 @@ -#!/usr/bin/env python3 +# Copyright 2020-2024 VyOS maintainers and contributors <maintainers@vyos.io> # -# Copyright (C) 2020 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 program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, +# 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 General Public License for more details. +# 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 General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. -from sys import argv,exit from vyos.configtree import ConfigTree -if len(argv) < 2: - print("Must specify file name!") - exit(1) - -file_name = argv[1] - -with open(file_name, 'r') as f: - config_file = f.read() - -config = ConfigTree(config_file) - def merge_npt(config,base,rule): merge_base = ['nat66','source','rule',rule] # Configure migration functions if config.exists(base + ['description']): tmp = config.return_value(base + ['description']) config.set(merge_base + ['description'],value=tmp) if config.exists(base + ['disable']): tmp = config.return_value(base + ['disable']) config.set(merge_base + ['disable'],value=tmp) if config.exists(base + ['outbound-interface']): tmp = config.return_value(base + ['outbound-interface']) config.set(merge_base + ['outbound-interface'],value=tmp) if config.exists(base + ['source','prefix']): tmp = config.return_value(base + ['source','prefix']) config.set(merge_base + ['source','prefix'],value=tmp) if config.exists(base + ['translation','prefix']): tmp = config.return_value(base + ['translation','prefix']) config.set(merge_base + ['translation','address'],value=tmp) -if not config.exists(['nat', 'nptv6']): - # Nothing to do - exit(0) - -for rule in config.list_nodes(['nat', 'nptv6', 'rule']): - base = ['nat', 'nptv6', 'rule', rule] - # Merge 'nat nptv6' to 'nat66 source' - merge_npt(config,base,rule) +def migrate(config: ConfigTree) -> None: + if not config.exists(['nat', 'nptv6']): + # Nothing to do + return -# Delete the original NPT configuration -config.delete(['nat','nptv6']); + for rule in config.list_nodes(['nat', 'nptv6', 'rule']): + base = ['nat', 'nptv6', 'rule', rule] + # Merge 'nat nptv6' to 'nat66 source' + merge_npt(config,base,rule) -try: - with open(file_name, 'w') as f: - f.write(config.to_string()) -except OSError as e: - print("Failed to save the modified config: {}".format(e)) - exit(1) + # Delete the original NPT configuration + config.delete(['nat','nptv6']); diff --git a/src/migration-scripts/nat66/1-to-2 b/src/migration-scripts/nat66/1-to-2 old mode 100755 new mode 100644 index b7d4e3f6b..f49940ae0 --- a/src/migration-scripts/nat66/1-to-2 +++ b/src/migration-scripts/nat66/1-to-2 @@ -1,63 +1,61 @@ #!/usr/bin/env python3 # # Copyright (C) 2023 VyOS maintainers and contributors # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License version 2 or later as # published by the Free Software Foundation. # # This program 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 General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. +# Copyright 2023-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# 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 <http://www.gnu.org/licenses/>. + # T5681: Firewall re-writing. Simplify cli when mathcing interface # From # 'set nat66 [source|destination] rule X [inbound-interface|outbound interface] <iface>' # to # 'set nat66 [source|destination] rule X [inbound-interface|outbound interface] name <iface>' -from sys import argv,exit from vyos.configtree import ConfigTree -if len(argv) < 2: - print("Must specify file name!") - exit(1) - -file_name = argv[1] - -with open(file_name, 'r') as f: - config_file = f.read() - -config = ConfigTree(config_file) -if not config.exists(['nat66']): - # Nothing to do - exit(0) - -for direction in ['source', 'destination']: - # If a node doesn't exist, we obviously have nothing to do. - if not config.exists(['nat66', direction]): - continue - - # However, we also need to handle the case when a 'source' or 'destination' sub-node does exist, - # but there are no rules under it. - if not config.list_nodes(['nat66', direction]): - continue - - for rule in config.list_nodes(['nat66', direction, 'rule']): - base = ['nat66', direction, 'rule', rule] - for iface in ['inbound-interface','outbound-interface']: - if config.exists(base + [iface]): - tmp = config.return_value(base + [iface]) - config.delete(base + [iface]) - config.set(base + [iface, 'name'], value=tmp) - -try: - with open(file_name, 'w') as f: - f.write(config.to_string()) -except OSError as e: - print("Failed to save the modified config: {}".format(e)) - exit(1) +def migrate(config: ConfigTree) -> None: + if not config.exists(['nat66']): + # Nothing to do + return + + for direction in ['source', 'destination']: + # If a node doesn't exist, we obviously have nothing to do. + if not config.exists(['nat66', direction]): + continue + + # However, we also need to handle the case when a 'source' or 'destination' sub-node does exist, + # but there are no rules under it. + if not config.list_nodes(['nat66', direction]): + continue + + for rule in config.list_nodes(['nat66', direction, 'rule']): + base = ['nat66', direction, 'rule', rule] + for iface in ['inbound-interface','outbound-interface']: + if config.exists(base + [iface]): + tmp = config.return_value(base + [iface]) + config.delete(base + [iface]) + config.set(base + [iface, 'name'], value=tmp) diff --git a/src/migration-scripts/nat66/2-to-3 b/src/migration-scripts/nat66/2-to-3 old mode 100755 new mode 100644 index f34f170b3..55d5f4b2b --- a/src/migration-scripts/nat66/2-to-3 +++ b/src/migration-scripts/nat66/2-to-3 @@ -1,61 +1,45 @@ -#!/usr/bin/env python3 +# Copyright 2023-2024 VyOS maintainers and contributors <maintainers@vyos.io> # -# Copyright (C) 2023 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 program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, +# 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 General Public License for more details. +# 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 General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. - -from sys import argv,exit -from vyos.configtree import ConfigTree +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. -if len(argv) < 2: - print("Must specify file name!") - exit(1) +# T2898: add ndp-proxy service -file_name = argv[1] - -with open(file_name, 'r') as f: - config_file = f.read() +from vyos.configtree import ConfigTree base = ['nat66', 'source'] new_base = ['service', 'ndp-proxy', 'interface'] -config = ConfigTree(config_file) -if not config.exists(base): - # Nothing to do - exit(0) - -for rule in config.list_nodes(base + ['rule']): - base_rule = base + ['rule', rule] - - interface = None - if config.exists(base_rule + ['outbound-interface', 'name']): - interface = config.return_value(base_rule + ['outbound-interface', 'name']) - else: - continue - - prefix_base = base_rule + ['source', 'prefix'] - if config.exists(prefix_base): - prefix = config.return_value(prefix_base) - config.set(new_base + [interface, 'prefix', prefix, 'mode'], value='static') - config.set_tag(new_base) - config.set_tag(new_base + [interface, 'prefix']) - - if config.exists(base_rule + ['disable']): - config.set(new_base + [interface, 'prefix', prefix, 'disable']) - -try: - with open(file_name, 'w') as f: - f.write(config.to_string()) -except OSError as e: - print("Failed to save the modified config: {}".format(e)) - exit(1) +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + # Nothing to do + return + + for rule in config.list_nodes(base + ['rule']): + base_rule = base + ['rule', rule] + + interface = None + if config.exists(base_rule + ['outbound-interface', 'name']): + interface = config.return_value(base_rule + ['outbound-interface', 'name']) + else: + continue + + prefix_base = base_rule + ['source', 'prefix'] + if config.exists(prefix_base): + prefix = config.return_value(prefix_base) + config.set(new_base + [interface, 'prefix', prefix, 'mode'], value='static') + config.set_tag(new_base) + config.set_tag(new_base + [interface, 'prefix']) + + if config.exists(base_rule + ['disable']): + config.set(new_base + [interface, 'prefix', prefix, 'disable']) diff --git a/src/migration-scripts/ntp/0-to-1 b/src/migration-scripts/ntp/0-to-1 old mode 100755 new mode 100644 index cbce45b9b..01f5a460a --- a/src/migration-scripts/ntp/0-to-1 +++ b/src/migration-scripts/ntp/0-to-1 @@ -1,36 +1,32 @@ #!/usr/bin/env python3 -# Delete "set system ntp server <n> dynamic" option +# Copyright 2018-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# 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 <http://www.gnu.org/licenses/>. -import sys +# Delete "set system ntp server <n> dynamic" option from vyos.configtree import ConfigTree -if len(sys.argv) < 2: - print("Must specify file name!") - sys.exit(1) - -file_name = sys.argv[1] +def migrate(config: ConfigTree) -> None: + if not config.exists(['system', 'ntp', 'server']): + # Nothing to do + return -with open(file_name, 'r') as f: - config_file = f.read() - -config = ConfigTree(config_file) - -if not config.exists(['system', 'ntp', 'server']): - # Nothing to do - sys.exit(0) -else: # Delete abandoned leaf node if found inside tag node for # "set system ntp server <n> dynamic" base = ['system', 'ntp', 'server'] for server in config.list_nodes(base): if config.exists(base + [server, 'dynamic']): config.delete(base + [server, 'dynamic']) - - try: - with open(file_name, 'w') as f: - f.write(config.to_string()) - except OSError as e: - print("Failed to save the modified config: {}".format(e)) - sys.exit(1) diff --git a/src/migration-scripts/ntp/1-to-2 b/src/migration-scripts/ntp/1-to-2 old mode 100755 new mode 100644 index fd1f15d91..fd7b08221 --- a/src/migration-scripts/ntp/1-to-2 +++ b/src/migration-scripts/ntp/1-to-2 @@ -1,72 +1,53 @@ -#!/usr/bin/env python3 - -# Copyright (C) 2023 VyOS maintainers and contributors +# Copyright 2023-2024 VyOS maintainers and contributors <maintainers@vyos.io> # -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. +# 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 program is distributed in the hope that it will be useful, +# 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 General Public License for more details. +# 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 General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. # T3008: move from ntpd to chrony and migrate "system ntp" to "service ntp" -import sys - from vyos.configtree import ConfigTree -if len(sys.argv) < 2: - print("Must specify file name!") - sys.exit(1) - -file_name = sys.argv[1] - -with open(file_name, 'r') as f: - config_file = f.read() - -config = ConfigTree(config_file) - base_path = ['system', 'ntp'] new_base_path = ['service', 'ntp'] -if not config.exists(base_path): - # Nothing to do - sys.exit(0) - -# config.copy does not recursively create a path, so create ['service'] if -# it doesn't yet exist, such as for config.boot.default -if not config.exists(['service']): - config.set(['service']) - -# copy "system ntp" to "service ntp" -config.copy(base_path, new_base_path) -config.delete(base_path) - -# chrony does not support the preempt option, drop it -for server in config.list_nodes(new_base_path + ['server']): - server_base = new_base_path + ['server', server] - if config.exists(server_base + ['preempt']): - config.delete(server_base + ['preempt']) - -# Rename "allow-clients" -> "allow-client" -if config.exists(new_base_path + ['allow-clients']): - config.rename(new_base_path + ['allow-clients'], 'allow-client') - -# By default VyOS 1.3 allowed NTP queries for all networks - in chrony we -# explicitly disable this behavior and clients need to be specified using the -# allow-client CLI option. In order to be fully backwards compatible, we specify -# 0.0.0.0/0 and ::/0 as allow networks if not specified otherwise explicitly. -if not config.exists(new_base_path + ['allow-client']): - config.set(new_base_path + ['allow-client', 'address'], value='0.0.0.0/0', replace=False) - config.set(new_base_path + ['allow-client', 'address'], value='::/0', replace=False) -try: - with open(file_name, 'w') as f: - f.write(config.to_string()) -except OSError as e: - print("Failed to save the modified config: {}".format(e)) - sys.exit(1) +def migrate(config: ConfigTree) -> None: + if not config.exists(base_path): + # Nothing to do + return + + # config.copy does not recursively create a path, so create ['service'] if + # it doesn't yet exist, such as for config.boot.default + if not config.exists(['service']): + config.set(['service']) + + # copy "system ntp" to "service ntp" + config.copy(base_path, new_base_path) + config.delete(base_path) + + # chrony does not support the preempt option, drop it + for server in config.list_nodes(new_base_path + ['server']): + server_base = new_base_path + ['server', server] + if config.exists(server_base + ['preempt']): + config.delete(server_base + ['preempt']) + + # Rename "allow-clients" -> "allow-client" + if config.exists(new_base_path + ['allow-clients']): + config.rename(new_base_path + ['allow-clients'], 'allow-client') + + # By default VyOS 1.3 allowed NTP queries for all networks - in chrony we + # explicitly disable this behavior and clients need to be specified using the + # allow-client CLI option. In order to be fully backwards compatible, we specify + # 0.0.0.0/0 and ::/0 as allow networks if not specified otherwise explicitly. + if not config.exists(new_base_path + ['allow-client']): + config.set(new_base_path + ['allow-client', 'address'], value='0.0.0.0/0', replace=False) + config.set(new_base_path + ['allow-client', 'address'], value='::/0', replace=False) diff --git a/src/migration-scripts/ntp/2-to-3 b/src/migration-scripts/ntp/2-to-3 old mode 100755 new mode 100644 index a4351845e..bbda90351 --- a/src/migration-scripts/ntp/2-to-3 +++ b/src/migration-scripts/ntp/2-to-3 @@ -1,62 +1,43 @@ -#!/usr/bin/env python3 - -# Copyright (C) 2023 VyOS maintainers and contributors +# Copyright 2023-2024 VyOS maintainers and contributors <maintainers@vyos.io> # -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. +# 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 program is distributed in the hope that it will be useful, +# 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 General Public License for more details. +# 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 General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. # T5154: allow only one ip address per family for parameter 'listen-address' # Allow only one interface for parameter 'interface' # If more than one are specified, remove such entries -import sys - from vyos.configtree import ConfigTree from vyos.template import is_ipv4 from vyos.template import is_ipv6 -if len(sys.argv) < 2: - print("Must specify file name!") - sys.exit(1) - -file_name = sys.argv[1] - -with open(file_name, 'r') as f: - config_file = f.read() - -config = ConfigTree(config_file) - base_path = ['service', 'ntp'] -if not config.exists(base_path): - # Nothing to do - sys.exit(0) -if config.exists(base_path + ['listen-address']) and (len([addr for addr in config.return_values(base_path + ['listen-address']) if is_ipv4(addr)]) > 1): - for addr in config.return_values(base_path + ['listen-address']): - if is_ipv4(addr): - config.delete_value(base_path + ['listen-address'], addr) +def migrate(config: ConfigTree) -> None: + if not config.exists(base_path): + # Nothing to do + return -if config.exists(base_path + ['listen-address']) and (len([addr for addr in config.return_values(base_path + ['listen-address']) if is_ipv6(addr)]) > 1): - for addr in config.return_values(base_path + ['listen-address']): - if is_ipv6(addr): - config.delete_value(base_path + ['listen-address'], addr) + if config.exists(base_path + ['listen-address']) and (len([addr for addr in config.return_values(base_path + ['listen-address']) if is_ipv4(addr)]) > 1): + for addr in config.return_values(base_path + ['listen-address']): + if is_ipv4(addr): + config.delete_value(base_path + ['listen-address'], addr) -if config.exists(base_path + ['interface']): - if len(config.return_values(base_path + ['interface'])) > 1: - config.delete(base_path + ['interface']) + if config.exists(base_path + ['listen-address']) and (len([addr for addr in config.return_values(base_path + ['listen-address']) if is_ipv6(addr)]) > 1): + for addr in config.return_values(base_path + ['listen-address']): + if is_ipv6(addr): + config.delete_value(base_path + ['listen-address'], addr) -try: - with open(file_name, 'w') as f: - f.write(config.to_string()) -except OSError as e: - print("Failed to save the modified config: {}".format(e)) - sys.exit(1) + if config.exists(base_path + ['interface']): + if len(config.return_values(base_path + ['interface'])) > 1: + config.delete(base_path + ['interface']) diff --git a/src/migration-scripts/openconnect/0-to-1 b/src/migration-scripts/openconnect/0-to-1 old mode 100755 new mode 100644 index c64b16cb2..aa5a97eee --- a/src/migration-scripts/openconnect/0-to-1 +++ b/src/migration-scripts/openconnect/0-to-1 @@ -1,135 +1,116 @@ -#!/usr/bin/env python3 +# Copyright 2021-2024 VyOS maintainers and contributors <maintainers@vyos.io> # -# Copyright (C) 2021-2024 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 program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, +# 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 General Public License for more details. +# 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 General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. # - Update SSL to use PKI configuration import os -from sys import argv -from sys import exit from vyos.configtree import ConfigTree from vyos.pki import load_certificate from vyos.pki import load_private_key from vyos.pki import encode_certificate from vyos.pki import encode_private_key from vyos.utils.process import run -if len(argv) < 2: - print("Must specify file name!") - exit(1) - -file_name = argv[1] - -with open(file_name, 'r') as f: - config_file = f.read() - -config = ConfigTree(config_file) base = ['vpn', 'openconnect'] pki_base = ['pki'] -if not config.exists(base): - exit(0) - AUTH_DIR = '/config/auth' def wrapped_pem_to_config_value(pem): return "".join(pem.strip().split("\n")[1:-1]) -if not config.exists(base + ['ssl']): - exit(0) - -x509_base = base + ['ssl'] -pki_name = 'openconnect' - -if not config.exists(pki_base + ['ca']): - config.set(pki_base + ['ca']) - config.set_tag(pki_base + ['ca']) - -if not config.exists(pki_base + ['certificate']): - config.set(pki_base + ['certificate']) - config.set_tag(pki_base + ['certificate']) - -if config.exists(x509_base + ['ca-cert-file']): - cert_file = config.return_value(x509_base + ['ca-cert-file']) - cert_path = os.path.join(AUTH_DIR, cert_file) - cert = None - - if os.path.isfile(cert_path): - if not os.access(cert_path, os.R_OK): - run(f'sudo chmod 644 {cert_path}') - - with open(cert_path, 'r') as f: - cert_data = f.read() - cert = load_certificate(cert_data, wrap_tags=False) - - if cert: - cert_pem = encode_certificate(cert) - config.set(pki_base + ['ca', pki_name, 'certificate'], value=wrapped_pem_to_config_value(cert_pem)) - config.set(x509_base + ['ca-certificate'], value=pki_name) - else: - print(f'Failed to migrate CA certificate on openconnect config') - - config.delete(x509_base + ['ca-cert-file']) - -if config.exists(x509_base + ['cert-file']): - cert_file = config.return_value(x509_base + ['cert-file']) - cert_path = os.path.join(AUTH_DIR, cert_file) - cert = None - - if os.path.isfile(cert_path): - if not os.access(cert_path, os.R_OK): - run(f'sudo chmod 644 {cert_path}') - - with open(cert_path, 'r') as f: - cert_data = f.read() - cert = load_certificate(cert_data, wrap_tags=False) - - if cert: - cert_pem = encode_certificate(cert) - config.set(pki_base + ['certificate', pki_name, 'certificate'], value=wrapped_pem_to_config_value(cert_pem)) - config.set(x509_base + ['certificate'], value=pki_name) - else: - print(f'Failed to migrate certificate on openconnect config') - - config.delete(x509_base + ['cert-file']) - -if config.exists(x509_base + ['key-file']): - key_file = config.return_value(x509_base + ['key-file']) - key_path = os.path.join(AUTH_DIR, key_file) - key = None - - if os.path.isfile(key_path): - if not os.access(key_path, os.R_OK): - run(f'sudo chmod 644 {key_path}') - - with open(key_path, 'r') as f: - key_data = f.read() - key = load_private_key(key_data, passphrase=None, wrap_tags=False) - - if key: - key_pem = encode_private_key(key, passphrase=None) - config.set(pki_base + ['certificate', pki_name, 'private', 'key'], value=wrapped_pem_to_config_value(key_pem)) - else: - print(f'Failed to migrate private key on openconnect config') - - config.delete(x509_base + ['key-file']) - -try: - with open(file_name, 'w') as f: - f.write(config.to_string()) -except OSError as e: - print("Failed to save the modified config: {}".format(e)) - exit(1) +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + return + + if not config.exists(base + ['ssl']): + return + + x509_base = base + ['ssl'] + pki_name = 'openconnect' + + if not config.exists(pki_base + ['ca']): + config.set(pki_base + ['ca']) + config.set_tag(pki_base + ['ca']) + + if not config.exists(pki_base + ['certificate']): + config.set(pki_base + ['certificate']) + config.set_tag(pki_base + ['certificate']) + + if config.exists(x509_base + ['ca-cert-file']): + cert_file = config.return_value(x509_base + ['ca-cert-file']) + cert_path = os.path.join(AUTH_DIR, cert_file) + cert = None + + if os.path.isfile(cert_path): + if not os.access(cert_path, os.R_OK): + run(f'sudo chmod 644 {cert_path}') + + with open(cert_path, 'r') as f: + cert_data = f.read() + cert = load_certificate(cert_data, wrap_tags=False) + + if cert: + cert_pem = encode_certificate(cert) + config.set(pki_base + ['ca', pki_name, 'certificate'], value=wrapped_pem_to_config_value(cert_pem)) + config.set(x509_base + ['ca-certificate'], value=pki_name) + else: + print(f'Failed to migrate CA certificate on openconnect config') + + config.delete(x509_base + ['ca-cert-file']) + + if config.exists(x509_base + ['cert-file']): + cert_file = config.return_value(x509_base + ['cert-file']) + cert_path = os.path.join(AUTH_DIR, cert_file) + cert = None + + if os.path.isfile(cert_path): + if not os.access(cert_path, os.R_OK): + run(f'sudo chmod 644 {cert_path}') + + with open(cert_path, 'r') as f: + cert_data = f.read() + cert = load_certificate(cert_data, wrap_tags=False) + + if cert: + cert_pem = encode_certificate(cert) + config.set(pki_base + ['certificate', pki_name, 'certificate'], value=wrapped_pem_to_config_value(cert_pem)) + config.set(x509_base + ['certificate'], value=pki_name) + else: + print(f'Failed to migrate certificate on openconnect config') + + config.delete(x509_base + ['cert-file']) + + if config.exists(x509_base + ['key-file']): + key_file = config.return_value(x509_base + ['key-file']) + key_path = os.path.join(AUTH_DIR, key_file) + key = None + + if os.path.isfile(key_path): + if not os.access(key_path, os.R_OK): + run(f'sudo chmod 644 {key_path}') + + with open(key_path, 'r') as f: + key_data = f.read() + key = load_private_key(key_data, passphrase=None, wrap_tags=False) + + if key: + key_pem = encode_private_key(key, passphrase=None) + config.set(pki_base + ['certificate', pki_name, 'private', 'key'], value=wrapped_pem_to_config_value(key_pem)) + else: + print(f'Failed to migrate private key on openconnect config') + + config.delete(x509_base + ['key-file']) diff --git a/src/migration-scripts/openconnect/1-to-2 b/src/migration-scripts/openconnect/1-to-2 old mode 100755 new mode 100644 index 7978aa56e..4f74b44df --- a/src/migration-scripts/openconnect/1-to-2 +++ b/src/migration-scripts/openconnect/1-to-2 @@ -1,54 +1,35 @@ -#!/usr/bin/env python3 +# Copyright 2022-2024 VyOS maintainers and contributors <maintainers@vyos.io> # -# Copyright (C) 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 program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, +# 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 General Public License for more details. +# 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 General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. # Delete depricated outside-nexthop address -import sys - from vyos.configtree import ConfigTree -if len(sys.argv) < 2: - print("Must specify file name!") - sys.exit(1) - -file_name = sys.argv[1] - -with open(file_name, 'r') as f: - config_file = f.read() - -config = ConfigTree(config_file) - cfg_base = ['vpn', 'openconnect'] -if not config.exists(cfg_base): - # Nothing to do - sys.exit(0) -else: +def migrate(config: ConfigTree) -> None: + if not config.exists(cfg_base): + # Nothing to do + return + if config.exists(cfg_base + ['authentication', 'mode']): if config.return_value(cfg_base + ['authentication', 'mode']) == 'radius': # if "mode value radius", change to "mode + valueless node radius" config.delete_value(cfg_base + ['authentication','mode'], 'radius') config.set(cfg_base + ['authentication', 'mode', 'radius'], value=None) elif config.return_value(cfg_base + ['authentication', 'mode']) == 'local': # if "mode local", change to "mode + node local value password" config.delete_value(cfg_base + ['authentication', 'mode'], 'local') config.set(cfg_base + ['authentication', 'mode', 'local'], value='password') - try: - with open(file_name, 'w') as f: - f.write(config.to_string()) - except OSError as e: - print("Failed to save the modified config: {}".format(e)) - sys.exit(1) diff --git a/src/migration-scripts/openconnect/2-to-3 b/src/migration-scripts/openconnect/2-to-3 old mode 100755 new mode 100644 index e78fc8a91..00e13ecb0 --- a/src/migration-scripts/openconnect/2-to-3 +++ b/src/migration-scripts/openconnect/2-to-3 @@ -1,50 +1,30 @@ -#!/usr/bin/env python3 +# Copyright 2024 VyOS maintainers and contributors <maintainers@vyos.io> # -# Copyright (C) 2024 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 program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, +# 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 General Public License for more details. +# 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 General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. # T4982: Retain prior default TLS version (v1.0) when upgrading installations with existing openconnect configurations -import sys - from vyos.configtree import ConfigTree -if len(sys.argv) < 2: - print("Must specify file name!") - sys.exit(1) - -file_name = sys.argv[1] - -with open(file_name, 'r') as f: - config_file = f.read() - - -config = ConfigTree(config_file) cfg_base = ['vpn', 'openconnect'] -# bail out early if service is unconfigured -if not config.exists(cfg_base): - sys.exit(0) - -# new default is TLS 1.2 - set explicit old default value of TLS 1.0 for upgraded configurations to keep compatibility -tls_min_path = cfg_base + ['tls-version-min'] -if not config.exists(tls_min_path): - config.set(tls_min_path, value='1.0') +def migrate(config: ConfigTree) -> None: + # bail out early if service is unconfigured + if not config.exists(cfg_base): + return -try: - with open(file_name, 'w') as f: - f.write(config.to_string()) -except OSError as e: - print("Failed to save the modified config: {}".format(e)) - sys.exit(1) + # new default is TLS 1.2 - set explicit old default value of TLS 1.0 for upgraded configurations to keep compatibility + tls_min_path = cfg_base + ['tls-version-min'] + if not config.exists(tls_min_path): + config.set(tls_min_path, value='1.0') diff --git a/src/migration-scripts/openvpn/0-to-1 b/src/migration-scripts/openvpn/0-to-1 old mode 100755 new mode 100644 index 24bb38d3c..e5db731ed --- a/src/migration-scripts/openvpn/0-to-1 +++ b/src/migration-scripts/openvpn/0-to-1 @@ -1,49 +1,43 @@ -#!/usr/bin/env python3 +# Copyright 2023-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# 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 <http://www.gnu.org/licenses/>. # Removes outdated ciphers (DES and Blowfish) from OpenVPN configs -import sys - from vyos.configtree import ConfigTree -if len(sys.argv) < 2: - print("Must specify file name!") - sys.exit(1) - -file_name = sys.argv[1] - -with open(file_name, 'r') as f: - config_file = f.read() +def migrate(config: ConfigTree) -> None: + if not config.exists(['interfaces', 'openvpn']): + # Nothing to do + return -config = ConfigTree(config_file) - -if not config.exists(['interfaces', 'openvpn']): - # Nothing to do - sys.exit(0) -else: ovpn_intfs = config.list_nodes(['interfaces', 'openvpn']) for i in ovpn_intfs: # Remove DES and Blowfish from 'encryption cipher' cipher_path = ['interfaces', 'openvpn', i, 'encryption', 'cipher'] if config.exists(cipher_path): cipher = config.return_value(cipher_path) if cipher in ['des', 'bf128', 'bf256']: config.delete(cipher_path) ncp_cipher_path = ['interfaces', 'openvpn', i, 'encryption', 'ncp-ciphers'] if config.exists(ncp_cipher_path): ncp_ciphers = config.return_values(['interfaces', 'openvpn', i, 'encryption', 'ncp-ciphers']) if 'des' in ncp_ciphers: config.delete_value(['interfaces', 'openvpn', i, 'encryption', 'ncp-ciphers'], 'des') # Clean up the encryption subtree if the migration procedure left it empty if config.exists(['interfaces', 'openvpn', i, 'encryption']) and \ (config.list_nodes(['interfaces', 'openvpn', i, 'encryption']) == []): config.delete(['interfaces', 'openvpn', i, 'encryption']) - - try: - with open(file_name, 'w') as f: - f.write(config.to_string()) - except OSError as e: - print("Failed to save the modified config: {}".format(e)) - sys.exit(1) diff --git a/src/migration-scripts/openvpn/1-to-2 b/src/migration-scripts/openvpn/1-to-2 old mode 100755 new mode 100644 index 1f82a2128..b7b7d4c77 --- a/src/migration-scripts/openvpn/1-to-2 +++ b/src/migration-scripts/openvpn/1-to-2 @@ -1,74 +1,55 @@ #!/usr/bin/env python3 # # Copyright (C) 2024 VyOS maintainers and contributors # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License version 2 or later as # published by the Free Software Foundation. # # This program 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 General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. # # Removes --cipher option (deprecated) from OpenVPN configs # and moves it to --data-ciphers for server and client modes -import sys - from vyos.configtree import ConfigTree -if len(sys.argv) < 2: - print("Must specify file name!") - sys.exit(1) - -file_name = sys.argv[1] - -with open(file_name, 'r') as f: - config_file = f.read() +def migrate(config: ConfigTree) -> None: + if not config.exists(['interfaces', 'openvpn']): + # Nothing to do + return -config = ConfigTree(config_file) - -if not config.exists(['interfaces', 'openvpn']): - # Nothing to do - sys.exit(0) -else: ovpn_intfs = config.list_nodes(['interfaces', 'openvpn']) for i in ovpn_intfs: # Remove 'encryption cipher' and add this value to 'encryption ncp-ciphers' # for server and client mode. # Site-to-site mode still can use --cipher option cipher_path = ['interfaces', 'openvpn', i, 'encryption', 'cipher'] ncp_cipher_path = ['interfaces', 'openvpn', i, 'encryption', 'ncp-ciphers'] if config.exists(cipher_path): if config.exists(['interfaces', 'openvpn', i, 'shared-secret-key']): continue cipher = config.return_value(cipher_path) config.delete(cipher_path) if cipher == 'none': if not config.exists(ncp_cipher_path): config.delete(['interfaces', 'openvpn', i, 'encryption']) continue ncp_ciphers = [] if config.exists(ncp_cipher_path): ncp_ciphers = config.return_values(ncp_cipher_path) config.delete(ncp_cipher_path) # need to add the deleted cipher at the first place in the list if cipher in ncp_ciphers: ncp_ciphers.remove(cipher) ncp_ciphers.insert(0, cipher) for c in ncp_ciphers: config.set(ncp_cipher_path, value=c, replace=False) - - try: - with open(file_name, 'w') as f: - f.write(config.to_string()) - except OSError as e: - print("Failed to save the modified config: {}".format(e)) - sys.exit(1) diff --git a/src/migration-scripts/ospf/0-to-1 b/src/migration-scripts/ospf/0-to-1 old mode 100755 new mode 100644 index 4085423a2..a1f810960 --- a/src/migration-scripts/ospf/0-to-1 +++ b/src/migration-scripts/ospf/0-to-1 @@ -1,84 +1,66 @@ -#!/usr/bin/env python3 +# Copyright 2021-2024 VyOS maintainers and contributors <maintainers@vyos.io> # -# Copyright (C) 2021-2024 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 program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, +# 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 General Public License for more details. +# 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 General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. # T3753: upgrade to FRR8 and move CLI options to better fit with the new FRR CLI -from sys import argv from vyos.configtree import ConfigTree def ospf_passive_migration(config, ospf_base): if config.exists(ospf_base): if config.exists(ospf_base + ['passive-interface']): default = False for interface in config.return_values(ospf_base + ['passive-interface']): if interface == 'default': default = True continue config.set(ospf_base + ['interface', interface, 'passive']) config.set_tag(ospf_base + ['interface']) config.delete(ospf_base + ['passive-interface']) if default: config.set(ospf_base + ['passive-interface'], value='default') if config.exists(ospf_base + ['passive-interface-exclude']): for interface in config.return_values(ospf_base + ['passive-interface-exclude']): config.set(ospf_base + ['interface', interface, 'passive', 'disable']) config.set_tag(ospf_base + ['interface']) config.delete(ospf_base + ['passive-interface-exclude']) -if len(argv) < 2: - print("Must specify file name!") - exit(1) - -file_name = argv[1] - -with open(file_name, 'r') as f: - config_file = f.read() - -config = ConfigTree(config_file) - ospfv3_base = ['protocols', 'ospfv3'] -if config.exists(ospfv3_base): - area_base = ospfv3_base + ['area'] - if config.exists(area_base): - for area in config.list_nodes(area_base): - if not config.exists(area_base + [area, 'interface']): - continue - for interface in config.return_values(area_base + [area, 'interface']): - config.set(ospfv3_base + ['interface', interface, 'area'], value=area) - config.set_tag(ospfv3_base + ['interface']) +def migrate(config: ConfigTree) -> None: + if config.exists(ospfv3_base): + area_base = ospfv3_base + ['area'] + if config.exists(area_base): + for area in config.list_nodes(area_base): + if not config.exists(area_base + [area, 'interface']): + continue - config.delete(area_base + [area, 'interface']) + for interface in config.return_values(area_base + [area, 'interface']): + config.set(ospfv3_base + ['interface', interface, 'area'], value=area) + config.set_tag(ospfv3_base + ['interface']) -# Migrate OSPF syntax in default VRF -ospf_base = ['protocols', 'ospf'] -ospf_passive_migration(config, ospf_base) + config.delete(area_base + [area, 'interface']) -vrf_base = ['vrf', 'name'] -if config.exists(vrf_base): - for vrf in config.list_nodes(vrf_base): - vrf_ospf_base = vrf_base + [vrf, 'protocols', 'ospf'] - if config.exists(vrf_ospf_base): - ospf_passive_migration(config, vrf_ospf_base) + # Migrate OSPF syntax in default VRF + ospf_base = ['protocols', 'ospf'] + ospf_passive_migration(config, ospf_base) -try: - with open(file_name, 'w') as f: - f.write(config.to_string()) -except OSError as e: - print(f'Failed to save the modified config: {e}') - exit(1) + vrf_base = ['vrf', 'name'] + if config.exists(vrf_base): + for vrf in config.list_nodes(vrf_base): + vrf_ospf_base = vrf_base + [vrf, 'protocols', 'ospf'] + if config.exists(vrf_ospf_base): + ospf_passive_migration(config, vrf_ospf_base) diff --git a/src/migration-scripts/ospf/1-to-2 b/src/migration-scripts/ospf/1-to-2 old mode 100755 new mode 100644 index ba9499c60..5368d8dd7 --- a/src/migration-scripts/ospf/1-to-2 +++ b/src/migration-scripts/ospf/1-to-2 @@ -1,80 +1,60 @@ -#!/usr/bin/env python3 +# Copyright 2023-2024 VyOS maintainers and contributors <maintainers@vyos.io> # -# Copyright (C) 2023 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 program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, +# 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 General Public License for more details. +# 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 General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. # T5150: Rework CLI definitions to apply route-maps between routing daemons # and zebra/kernel -from sys import argv -from sys import exit - from vyos.configtree import ConfigTree -if len(argv) < 2: - print("Must specify file name!") - exit(1) - -file_name = argv[1] - -with open(file_name, 'r') as f: - config_file = f.read() - -config = ConfigTree(config_file) - ospf_base = ['protocols', 'ospf'] -# Check if OSPF is configured - if so, migrate the CLI node -if config.exists(ospf_base): - if config.exists(ospf_base + ['route-map']): - tmp = config.return_value(ospf_base + ['route-map']) - - config.set(['system', 'ip', 'protocol', 'ospf', 'route-map'], value=tmp) - config.set_tag(['system', 'ip', 'protocol']) - config.delete(ospf_base + ['route-map']) - -ospfv3_base = ['protocols', 'ospfv3'] -# Check if OSPFv3 is configured - if so, migrate the CLI node -if config.exists(ospfv3_base): - if config.exists(ospfv3_base + ['route-map']): - tmp = config.return_value(ospfv3_base + ['route-map']) - - config.set(['system', 'ipv6', 'protocol', 'ospfv3', 'route-map'], value=tmp) - config.set_tag(['system', 'ipv6', 'protocol']) - config.delete(ospfv3_base + ['route-map']) - -# Check if vrf names are configured. Check if OSPF/OSPFv3 is configured - if so, -# migrate the CLI node(s) -if config.exists(['vrf', 'name']): - for vrf in config.list_nodes(['vrf', 'name']): - vrf_base = ['vrf', 'name', vrf] - if config.exists(vrf_base + ['protocols', 'ospf', 'route-map']): - tmp = config.return_value(vrf_base + ['protocols', 'ospf', 'route-map']) - - config.set(vrf_base + ['ip', 'protocol', 'ospf', 'route-map'], value=tmp) - config.set_tag(vrf_base + ['ip', 'protocol', 'ospf']) - config.delete(vrf_base + ['protocols', 'ospf', 'route-map']) - - if config.exists(vrf_base + ['protocols', 'ospfv3', 'route-map']): - tmp = config.return_value(vrf_base + ['protocols', 'ospfv3', 'route-map']) - - config.set(vrf_base + ['ipv6', 'protocol', 'ospfv3', 'route-map'], value=tmp) - config.set_tag(vrf_base + ['ipv6', 'protocol', 'ospfv6']) - config.delete(vrf_base + ['protocols', 'ospfv3', 'route-map']) -try: - with open(file_name, 'w') as f: - f.write(config.to_string()) -except OSError as e: - print(f'Failed to save the modified config: {e}') - exit(1) +def migrate(config: ConfigTree) -> None: + # Check if OSPF is configured - if so, migrate the CLI node + if config.exists(ospf_base): + if config.exists(ospf_base + ['route-map']): + tmp = config.return_value(ospf_base + ['route-map']) + + config.set(['system', 'ip', 'protocol', 'ospf', 'route-map'], value=tmp) + config.set_tag(['system', 'ip', 'protocol']) + config.delete(ospf_base + ['route-map']) + + ospfv3_base = ['protocols', 'ospfv3'] + # Check if OSPFv3 is configured - if so, migrate the CLI node + if config.exists(ospfv3_base): + if config.exists(ospfv3_base + ['route-map']): + tmp = config.return_value(ospfv3_base + ['route-map']) + + config.set(['system', 'ipv6', 'protocol', 'ospfv3', 'route-map'], value=tmp) + config.set_tag(['system', 'ipv6', 'protocol']) + config.delete(ospfv3_base + ['route-map']) + + # Check if vrf names are configured. Check if OSPF/OSPFv3 is configured - if so, + # migrate the CLI node(s) + if config.exists(['vrf', 'name']): + for vrf in config.list_nodes(['vrf', 'name']): + vrf_base = ['vrf', 'name', vrf] + if config.exists(vrf_base + ['protocols', 'ospf', 'route-map']): + tmp = config.return_value(vrf_base + ['protocols', 'ospf', 'route-map']) + + config.set(vrf_base + ['ip', 'protocol', 'ospf', 'route-map'], value=tmp) + config.set_tag(vrf_base + ['ip', 'protocol', 'ospf']) + config.delete(vrf_base + ['protocols', 'ospf', 'route-map']) + + if config.exists(vrf_base + ['protocols', 'ospfv3', 'route-map']): + tmp = config.return_value(vrf_base + ['protocols', 'ospfv3', 'route-map']) + + config.set(vrf_base + ['ipv6', 'protocol', 'ospfv3', 'route-map'], value=tmp) + config.set_tag(vrf_base + ['ipv6', 'protocol', 'ospfv6']) + config.delete(vrf_base + ['protocols', 'ospfv3', 'route-map']) diff --git a/src/migration-scripts/pim/0-to-1 b/src/migration-scripts/pim/0-to-1 old mode 100755 new mode 100644 index bf8af733c..ce24b23ba --- a/src/migration-scripts/pim/0-to-1 +++ b/src/migration-scripts/pim/0-to-1 @@ -1,72 +1,54 @@ -#!/usr/bin/env python3 +# Copyright 2023-2024 VyOS maintainers and contributors <maintainers@vyos.io> # -# Copyright (C) 2023 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 program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, +# 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 General Public License for more details. +# 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 General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. # T5736: igmp: migrate "protocols igmp" to "protocols pim" -import sys from vyos.configtree import ConfigTree -if len(sys.argv) < 2: - print("Must specify file name!") - sys.exit(1) - -file_name = sys.argv[1] - -with open(file_name, 'r') as f: - config_file = f.read() - -config = ConfigTree(config_file) - base = ['protocols', 'igmp'] pim_base = ['protocols', 'pim'] -if not config.exists(base): - # Nothing to do - sys.exit(0) -for interface in config.list_nodes(base + ['interface']): - base_igmp_iface = base + ['interface', interface] - pim_base_iface = pim_base + ['interface', interface] +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + # Nothing to do + return - # Create IGMP note under PIM interface - if not config.exists(pim_base_iface + ['igmp']): - config.set(pim_base_iface + ['igmp']) + for interface in config.list_nodes(base + ['interface']): + base_igmp_iface = base + ['interface', interface] + pim_base_iface = pim_base + ['interface', interface] - if config.exists(base_igmp_iface + ['join']): - config.copy(base_igmp_iface + ['join'], pim_base_iface + ['igmp', 'join']) - config.set_tag(pim_base_iface + ['igmp', 'join']) + # Create IGMP note under PIM interface + if not config.exists(pim_base_iface + ['igmp']): + config.set(pim_base_iface + ['igmp']) - new_join_base = pim_base_iface + ['igmp', 'join'] - for address in config.list_nodes(new_join_base): - if config.exists(new_join_base + [address, 'source']): - config.rename(new_join_base + [address, 'source'], 'source-address') + if config.exists(base_igmp_iface + ['join']): + config.copy(base_igmp_iface + ['join'], pim_base_iface + ['igmp', 'join']) + config.set_tag(pim_base_iface + ['igmp', 'join']) - if config.exists(base_igmp_iface + ['query-interval']): - config.copy(base_igmp_iface + ['query-interval'], pim_base_iface + ['igmp', 'query-interval']) + new_join_base = pim_base_iface + ['igmp', 'join'] + for address in config.list_nodes(new_join_base): + if config.exists(new_join_base + [address, 'source']): + config.rename(new_join_base + [address, 'source'], 'source-address') - if config.exists(base_igmp_iface + ['query-max-response-time']): - config.copy(base_igmp_iface + ['query-max-response-time'], pim_base_iface + ['igmp', 'query-max-response-time']) + if config.exists(base_igmp_iface + ['query-interval']): + config.copy(base_igmp_iface + ['query-interval'], pim_base_iface + ['igmp', 'query-interval']) - if config.exists(base_igmp_iface + ['version']): - config.copy(base_igmp_iface + ['version'], pim_base_iface + ['igmp', 'version']) + if config.exists(base_igmp_iface + ['query-max-response-time']): + config.copy(base_igmp_iface + ['query-max-response-time'], pim_base_iface + ['igmp', 'query-max-response-time']) -config.delete(base) + if config.exists(base_igmp_iface + ['version']): + config.copy(base_igmp_iface + ['version'], pim_base_iface + ['igmp', 'version']) -try: - with open(file_name, 'w') as f: - f.write(config.to_string()) -except OSError as e: - print("Failed to save the modified config: {}".format(e)) - sys.exit(1) + config.delete(base) diff --git a/src/migration-scripts/policy/0-to-1 b/src/migration-scripts/policy/0-to-1 old mode 100755 new mode 100644 index 8508b734a..837946c37 --- a/src/migration-scripts/policy/0-to-1 +++ b/src/migration-scripts/policy/0-to-1 @@ -1,65 +1,43 @@ -#!/usr/bin/env python3 +# Copyright 2021-2024 VyOS maintainers and contributors <maintainers@vyos.io> # -# Copyright (C) 2021 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 program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, +# 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 General Public License for more details. +# 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 General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. # T3631: route-map: migrate "set extcommunity-rt" and "set extcommunity-soo" # to "set extcommunity rt|soo" to match FRR syntax - -from sys import argv -from sys import exit - from vyos.configtree import ConfigTree -if len(argv) < 2: - print("Must specify file name!") - exit(1) - -file_name = argv[1] - -with open(file_name, 'r') as f: - config_file = f.read() - base = ['policy', 'route-map'] -config = ConfigTree(config_file) - -if not config.exists(base): - # Nothing to do - exit(0) - -for route_map in config.list_nodes(base): - if not config.exists(base + [route_map, 'rule']): - continue - for rule in config.list_nodes(base + [route_map, 'rule']): - base_rule = base + [route_map, 'rule', rule] +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + # Nothing to do + return - if config.exists(base_rule + ['set', 'extcommunity-rt']): - tmp = config.return_value(base_rule + ['set', 'extcommunity-rt']) - config.delete(base_rule + ['set', 'extcommunity-rt']) - config.set(base_rule + ['set', 'extcommunity', 'rt'], value=tmp) + for route_map in config.list_nodes(base): + if not config.exists(base + [route_map, 'rule']): + continue + for rule in config.list_nodes(base + [route_map, 'rule']): + base_rule = base + [route_map, 'rule', rule] + if config.exists(base_rule + ['set', 'extcommunity-rt']): + tmp = config.return_value(base_rule + ['set', 'extcommunity-rt']) + config.delete(base_rule + ['set', 'extcommunity-rt']) + config.set(base_rule + ['set', 'extcommunity', 'rt'], value=tmp) - if config.exists(base_rule + ['set', 'extcommunity-soo']): - tmp = config.return_value(base_rule + ['set', 'extcommunity-soo']) - config.delete(base_rule + ['set', 'extcommunity-soo']) - config.set(base_rule + ['set', 'extcommunity', 'soo'], value=tmp) -try: - with open(file_name, 'w') as f: - f.write(config.to_string()) -except OSError as e: - print(f'Failed to save the modified config: {e}') - exit(1) + if config.exists(base_rule + ['set', 'extcommunity-soo']): + tmp = config.return_value(base_rule + ['set', 'extcommunity-soo']) + config.delete(base_rule + ['set', 'extcommunity-soo']) + config.set(base_rule + ['set', 'extcommunity', 'soo'], value=tmp) diff --git a/src/migration-scripts/policy/1-to-2 b/src/migration-scripts/policy/1-to-2 old mode 100755 new mode 100644 index c7a983bba..ba3e48db0 --- a/src/migration-scripts/policy/1-to-2 +++ b/src/migration-scripts/policy/1-to-2 @@ -1,86 +1,67 @@ -#!/usr/bin/env python3 +# Copyright 2022-2024 VyOS maintainers and contributors <maintainers@vyos.io> # -# Copyright (C) 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 program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, +# 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 General Public License for more details. +# 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 General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. # T4170: rename "policy ipv6-route" to "policy route6" to match common # IPv4/IPv6 schema # T4178: Update tcp flags to use multi value node -from sys import argv -from sys import exit - from vyos.configtree import ConfigTree -if len(argv) < 2: - print("Must specify file name!") - exit(1) - -file_name = argv[1] - -with open(file_name, 'r') as f: - config_file = f.read() - base = ['policy'] -config = ConfigTree(config_file) -if not config.exists(base): - # Nothing to do - exit(0) +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + # Nothing to do + return -if config.exists(base + ['ipv6-route']): - config.rename(base + ['ipv6-route'],'route6') - config.set_tag(['policy', 'route6']) + if config.exists(base + ['ipv6-route']): + config.rename(base + ['ipv6-route'],'route6') + config.set_tag(['policy', 'route6']) -for route in ['route', 'route6']: - if config.exists(base + [route]): - for name in config.list_nodes(base + [route]): - if config.exists(base + [route, name, 'rule']): - for rule in config.list_nodes(base + [route, name, 'rule']): - rule_tcp_flags = base + [route, name, 'rule', rule, 'tcp', 'flags'] + for route in ['route', 'route6']: + if config.exists(base + [route]): + for name in config.list_nodes(base + [route]): + if config.exists(base + [route, name, 'rule']): + for rule in config.list_nodes(base + [route, name, 'rule']): + rule_tcp_flags = base + [route, name, 'rule', rule, 'tcp', 'flags'] - if config.exists(rule_tcp_flags): - tmp = config.return_value(rule_tcp_flags) - config.delete(rule_tcp_flags) - for flag in tmp.split(","): + if config.exists(rule_tcp_flags): + tmp = config.return_value(rule_tcp_flags) + config.delete(rule_tcp_flags) for flag in tmp.split(","): - if flag[0] == '!': - config.set(rule_tcp_flags + ['not', flag[1:].lower()]) - else: - config.set(rule_tcp_flags + [flag.lower()]) + for flag in tmp.split(","): + if flag[0] == '!': + config.set(rule_tcp_flags + ['not', flag[1:].lower()]) + else: + config.set(rule_tcp_flags + [flag.lower()]) -if config.exists(['interfaces']): - def if_policy_rename(config, path): - if config.exists(path + ['policy', 'ipv6-route']): - config.rename(path + ['policy', 'ipv6-route'], 'route6') + if config.exists(['interfaces']): + def if_policy_rename(config, path): + if config.exists(path + ['policy', 'ipv6-route']): + config.rename(path + ['policy', 'ipv6-route'], 'route6') - for if_type in config.list_nodes(['interfaces']): - for ifname in config.list_nodes(['interfaces', if_type]): - if_path = ['interfaces', if_type, ifname] - if_policy_rename(config, if_path) + for if_type in config.list_nodes(['interfaces']): + for ifname in config.list_nodes(['interfaces', if_type]): + if_path = ['interfaces', if_type, ifname] + if_policy_rename(config, if_path) - for vif_type in ['vif', 'vif-s']: - if config.exists(if_path + [vif_type]): - for vifname in config.list_nodes(if_path + [vif_type]): - if_policy_rename(config, if_path + [vif_type, vifname]) + for vif_type in ['vif', 'vif-s']: + if config.exists(if_path + [vif_type]): + for vifname in config.list_nodes(if_path + [vif_type]): + if_policy_rename(config, if_path + [vif_type, vifname]) - if config.exists(if_path + [vif_type, vifname, 'vif-c']): - for vifcname in config.list_nodes(if_path + [vif_type, vifname, 'vif-c']): - if_policy_rename(config, if_path + [vif_type, vifname, 'vif-c', vifcname]) -try: - with open(file_name, 'w') as f: - f.write(config.to_string()) -except OSError as e: - print(f'Failed to save the modified config: {e}') - exit(1) + if config.exists(if_path + [vif_type, vifname, 'vif-c']): + for vifcname in config.list_nodes(if_path + [vif_type, vifname, 'vif-c']): + if_policy_rename(config, if_path + [vif_type, vifname, 'vif-c', vifcname]) diff --git a/src/migration-scripts/policy/2-to-3 b/src/migration-scripts/policy/2-to-3 old mode 100755 new mode 100644 index 8a62c8e6f..399a55387 --- a/src/migration-scripts/policy/2-to-3 +++ b/src/migration-scripts/policy/2-to-3 @@ -1,58 +1,38 @@ -#!/usr/bin/env python3 +# Copyright 2022-2024 VyOS maintainers and contributors <maintainers@vyos.io> # -# Copyright (C) 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 program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, +# 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 General Public License for more details. +# 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 General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. # T3976: change cli # from: set policy route-map FOO rule 10 match ipv6 nexthop 'h:h:h:h:h:h:h:h' # to: set policy route-map FOO rule 10 match ipv6 nexthop address 'h:h:h:h:h:h:h:h' -from sys import argv -from sys import exit - from vyos.configtree import ConfigTree -if len(argv) < 2: - print("Must specify file name!") - exit(1) - -file_name = argv[1] - -with open(file_name, 'r') as f: - config_file = f.read() - base = ['policy', 'route-map'] -config = ConfigTree(config_file) - -if not config.exists(base): - # Nothing to do - exit(0) - -for route_map in config.list_nodes(base): - if not config.exists(base + [route_map, 'rule']): - continue - for rule in config.list_nodes(base + [route_map, 'rule']): - base_rule = base + [route_map, 'rule', rule] - - if config.exists(base_rule + ['match', 'ipv6', 'nexthop']): - tmp = config.return_value(base_rule + ['match', 'ipv6', 'nexthop']) - config.delete(base_rule + ['match', 'ipv6', 'nexthop']) - config.set(base_rule + ['match', 'ipv6', 'nexthop', 'address'], value=tmp) -try: - with open(file_name, 'w') as f: - f.write(config.to_string()) -except OSError as e: - print(f'Failed to save the modified config: {e}') - exit(1) \ No newline at end of file +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + # Nothing to do + return + + for route_map in config.list_nodes(base): + if not config.exists(base + [route_map, 'rule']): + continue + for rule in config.list_nodes(base + [route_map, 'rule']): + base_rule = base + [route_map, 'rule', rule] + + if config.exists(base_rule + ['match', 'ipv6', 'nexthop']): + tmp = config.return_value(base_rule + ['match', 'ipv6', 'nexthop']) + config.delete(base_rule + ['match', 'ipv6', 'nexthop']) + config.set(base_rule + ['match', 'ipv6', 'nexthop', 'address'], value=tmp) diff --git a/src/migration-scripts/policy/3-to-4 b/src/migration-scripts/policy/3-to-4 old mode 100755 new mode 100644 index 476fa3af2..5d4959def --- a/src/migration-scripts/policy/3-to-4 +++ b/src/migration-scripts/policy/3-to-4 @@ -1,162 +1,143 @@ -#!/usr/bin/env python3 +# Copyright 2022-2024 VyOS maintainers and contributors <maintainers@vyos.io> # -# Copyright (C) 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 program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, +# 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 General Public License for more details. +# 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 General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. # T4660: change cli # from: set policy route-map FOO rule 10 set community 'TEXT' # Multiple value # to: set policy route-map FOO rule 10 set community replace <community> # Multiple value # to: set policy route-map FOO rule 10 set community add <community> # to: set policy route-map FOO rule 10 set community none # # from: set policy route-map FOO rule 10 set large-community 'TEXT' # Multiple value # to: set policy route-map FOO rule 10 set large-community replace <community> # Multiple value # to: set policy route-map FOO rule 10 set large-community add <community> # to: set policy route-map FOO rule 10 set large-community none # # from: set policy route-map FOO rule 10 set extecommunity [rt|soo] 'TEXT' # Multiple value # to: set policy route-map FOO rule 10 set extcommunity [rt|soo] <community> -from sys import argv -from sys import exit - from vyos.configtree import ConfigTree # Migration function for large and regular communities def community_migrate(config: ConfigTree, rule: list[str]) -> bool: """ :param config: configuration object :type config: ConfigTree :param rule: Path to variable :type rule: list[str] :return: True if additive presents in community string :rtype: bool """ community_list = list((config.return_value(rule)).split(" ")) config.delete(rule) if 'none' in community_list: config.set(rule + ['none']) return False else: community_action: str = 'replace' if 'additive' in community_list: community_action = 'add' community_list.remove('additive') for community in community_list: config.set(rule + [community_action], value=community, replace=False) if community_action == 'replace': return False else: return True # Migration function for extcommunities def extcommunity_migrate(config: ConfigTree, rule: list[str]) -> None: """ :param config: configuration object :type config: ConfigTree :param rule: Path to variable :type rule: list[str] """ # if config.exists(rule + ['bandwidth']): # bandwidth: str = config.return_value(rule + ['bandwidth']) # config.delete(rule + ['bandwidth']) # config.set(rule + ['bandwidth'], value=bandwidth) if config.exists(rule + ['rt']): community_list = list((config.return_value(rule + ['rt'])).split(" ")) config.delete(rule + ['rt']) for community in community_list: config.set(rule + ['rt'], value=community, replace=False) if config.exists(rule + ['soo']): community_list = list((config.return_value(rule + ['soo'])).split(" ")) config.delete(rule + ['soo']) for community in community_list: config.set(rule + ['soo'], value=community, replace=False) -if len(argv) < 2: - print("Must specify file name!") - exit(1) - -file_name: str = argv[1] - -with open(file_name, 'r') as f: - config_file = f.read() - base: list[str] = ['policy', 'route-map'] -config = ConfigTree(config_file) - -if not config.exists(base): - # Nothing to do - exit(0) - -for route_map in config.list_nodes(base): - if not config.exists(base + [route_map, 'rule']): - continue - for rule in config.list_nodes(base + [route_map, 'rule']): - base_rule: list[str] = base + [route_map, 'rule', rule, 'set'] - - # IF additive presents in coummunity then comm-list is redundant - isAdditive: bool = True - #### Change Set community ######## - if config.exists(base_rule + ['community']): - isAdditive = community_migrate(config, - base_rule + ['community']) - - #### Change Set community-list delete migrate ######## - if config.exists(base_rule + ['comm-list', 'comm-list']): - if isAdditive: - tmp = config.return_value( - base_rule + ['comm-list', 'comm-list']) - config.delete(base_rule + ['comm-list']) - config.set(base_rule + ['community', 'delete'], value=tmp) - else: - config.delete(base_rule + ['comm-list']) - - isAdditive = False - #### Change Set large-community ######## - if config.exists(base_rule + ['large-community']): - isAdditive = community_migrate(config, - base_rule + ['large-community']) - - #### Change Set large-community delete by List ######## - if config.exists(base_rule + ['large-comm-list-delete']): - if isAdditive: - tmp = config.return_value( - base_rule + ['large-comm-list-delete']) - config.delete(base_rule + ['large-comm-list-delete']) - config.set(base_rule + ['large-community', 'delete'], - value=tmp) - else: - config.delete(base_rule + ['large-comm-list-delete']) - - #### Change Set extcommunity ######## - extcommunity_migrate(config, base_rule + ['extcommunity']) -try: - with open(file_name, 'w') as f: - f.write(config.to_string()) -except OSError as e: - print(f'Failed to save the modified config: {e}') - exit(1) + +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + # Nothing to do + return + + for route_map in config.list_nodes(base): + if not config.exists(base + [route_map, 'rule']): + continue + for rule in config.list_nodes(base + [route_map, 'rule']): + base_rule: list[str] = base + [route_map, 'rule', rule, 'set'] + + # IF additive presents in coummunity then comm-list is redundant + isAdditive: bool = True + #### Change Set community ######## + if config.exists(base_rule + ['community']): + isAdditive = community_migrate(config, + base_rule + ['community']) + + #### Change Set community-list delete migrate ######## + if config.exists(base_rule + ['comm-list', 'comm-list']): + if isAdditive: + tmp = config.return_value( + base_rule + ['comm-list', 'comm-list']) + config.delete(base_rule + ['comm-list']) + config.set(base_rule + ['community', 'delete'], value=tmp) + else: + config.delete(base_rule + ['comm-list']) + + isAdditive = False + #### Change Set large-community ######## + if config.exists(base_rule + ['large-community']): + isAdditive = community_migrate(config, + base_rule + ['large-community']) + + #### Change Set large-community delete by List ######## + if config.exists(base_rule + ['large-comm-list-delete']): + if isAdditive: + tmp = config.return_value( + base_rule + ['large-comm-list-delete']) + config.delete(base_rule + ['large-comm-list-delete']) + config.set(base_rule + ['large-community', 'delete'], + value=tmp) + else: + config.delete(base_rule + ['large-comm-list-delete']) + + #### Change Set extcommunity ######## + extcommunity_migrate(config, base_rule + ['extcommunity']) diff --git a/src/migration-scripts/policy/4-to-5 b/src/migration-scripts/policy/4-to-5 old mode 100755 new mode 100644 index 738850f67..0ecfdfd5e --- a/src/migration-scripts/policy/4-to-5 +++ b/src/migration-scripts/policy/4-to-5 @@ -1,135 +1,106 @@ -#!/usr/bin/env python3 +# Copyright 2022-2024 VyOS maintainers and contributors <maintainers@vyos.io> # -# Copyright (C) 2022-2024 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 program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, +# 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 General Public License for more details. +# 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 General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. # T2199: Migrate interface policy nodes to policy route <name> interface <ifname> -from sys import argv -from sys import exit - from vyos.configtree import ConfigTree -if len(argv) < 2: - print("Must specify file name!") - exit(1) - -file_name = argv[1] - -with open(file_name, 'r') as f: - config_file = f.read() - base4 = ['policy', 'route'] base6 = ['policy', 'route6'] -config = ConfigTree(config_file) - def delete_orphaned_interface_policy(config, iftype, ifname, vif=None, vifs=None, vifc=None): """Delete unexpected policy on interfaces in cases when policy does not exist but inreface has a policy configuration Example T5941: set interfaces bonding bond0 vif 995 policy """ if_path = ['interfaces', iftype, ifname] if vif: if_path += ['vif', vif] elif vifs: if_path += ['vif-s', vifs] if vifc: if_path += ['vif-c', vifc] if not config.exists(if_path + ['policy']): return config.delete(if_path + ['policy']) - -if not config.exists(base4) and not config.exists(base6): - # Delete orphaned nodes on interfaces T5941 - for iftype in config.list_nodes(['interfaces']): - for ifname in config.list_nodes(['interfaces', iftype]): - delete_orphaned_interface_policy(config, iftype, ifname) - - if config.exists(['interfaces', iftype, ifname, 'vif']): - for vif in config.list_nodes(['interfaces', iftype, ifname, 'vif']): - delete_orphaned_interface_policy(config, iftype, ifname, vif=vif) - - if config.exists(['interfaces', iftype, ifname, 'vif-s']): - for vifs in config.list_nodes(['interfaces', iftype, ifname, 'vif-s']): - delete_orphaned_interface_policy(config, iftype, ifname, vifs=vifs) - - if config.exists(['interfaces', iftype, ifname, 'vif-s', vifs, 'vif-c']): - for vifc in config.list_nodes(['interfaces', iftype, ifname, 'vif-s', vifs, 'vif-c']): - delete_orphaned_interface_policy(config, iftype, ifname, vifs=vifs, vifc=vifc) - - try: - with open(file_name, 'w') as f: - f.write(config.to_string()) - except OSError as e: - print("Failed to save the modified config: {}".format(e)) - exit(1) - - # Nothing to do - exit(0) - def migrate_interface(config, iftype, ifname, vif=None, vifs=None, vifc=None): if_path = ['interfaces', iftype, ifname] ifname_full = ifname if vif: if_path += ['vif', vif] ifname_full = f'{ifname}.{vif}' elif vifs: if_path += ['vif-s', vifs] ifname_full = f'{ifname}.{vifs}' if vifc: if_path += ['vif-c', vifc] ifname_full = f'{ifname}.{vifs}.{vifc}' if not config.exists(if_path + ['policy']): return if config.exists(if_path + ['policy', 'route']): route_name = config.return_value(if_path + ['policy', 'route']) config.set(base4 + [route_name, 'interface'], value=ifname_full, replace=False) if config.exists(if_path + ['policy', 'route6']): route_name = config.return_value(if_path + ['policy', 'route6']) config.set(base6 + [route_name, 'interface'], value=ifname_full, replace=False) config.delete(if_path + ['policy']) -for iftype in config.list_nodes(['interfaces']): - for ifname in config.list_nodes(['interfaces', iftype]): - migrate_interface(config, iftype, ifname) +def migrate(config: ConfigTree) -> None: + if not config.exists(base4) and not config.exists(base6): + # Delete orphaned nodes on interfaces T5941 + for iftype in config.list_nodes(['interfaces']): + for ifname in config.list_nodes(['interfaces', iftype]): + delete_orphaned_interface_policy(config, iftype, ifname) + + if config.exists(['interfaces', iftype, ifname, 'vif']): + for vif in config.list_nodes(['interfaces', iftype, ifname, 'vif']): + delete_orphaned_interface_policy(config, iftype, ifname, vif=vif) - if config.exists(['interfaces', iftype, ifname, 'vif']): - for vif in config.list_nodes(['interfaces', iftype, ifname, 'vif']): - migrate_interface(config, iftype, ifname, vif=vif) + if config.exists(['interfaces', iftype, ifname, 'vif-s']): + for vifs in config.list_nodes(['interfaces', iftype, ifname, 'vif-s']): + delete_orphaned_interface_policy(config, iftype, ifname, vifs=vifs) - if config.exists(['interfaces', iftype, ifname, 'vif-s']): - for vifs in config.list_nodes(['interfaces', iftype, ifname, 'vif-s']): - migrate_interface(config, iftype, ifname, vifs=vifs) + if config.exists(['interfaces', iftype, ifname, 'vif-s', vifs, 'vif-c']): + for vifc in config.list_nodes(['interfaces', iftype, ifname, 'vif-s', vifs, 'vif-c']): + delete_orphaned_interface_policy(config, iftype, ifname, vifs=vifs, vifc=vifc) - if config.exists(['interfaces', iftype, ifname, 'vif-s', vifs, 'vif-c']): - for vifc in config.list_nodes(['interfaces', iftype, ifname, 'vif-s', vifs, 'vif-c']): - migrate_interface(config, iftype, ifname, vifs=vifs, vifc=vifc) + # Nothing to do + return + + for iftype in config.list_nodes(['interfaces']): + for ifname in config.list_nodes(['interfaces', iftype]): + migrate_interface(config, iftype, ifname) -try: - with open(file_name, 'w') as f: - f.write(config.to_string()) -except OSError as e: - print("Failed to save the modified config: {}".format(e)) - exit(1) + if config.exists(['interfaces', iftype, ifname, 'vif']): + for vif in config.list_nodes(['interfaces', iftype, ifname, 'vif']): + migrate_interface(config, iftype, ifname, vif=vif) + + if config.exists(['interfaces', iftype, ifname, 'vif-s']): + for vifs in config.list_nodes(['interfaces', iftype, ifname, 'vif-s']): + migrate_interface(config, iftype, ifname, vifs=vifs) + + if config.exists(['interfaces', iftype, ifname, 'vif-s', vifs, 'vif-c']): + for vifc in config.list_nodes(['interfaces', iftype, ifname, 'vif-s', vifs, 'vif-c']): + migrate_interface(config, iftype, ifname, vifs=vifs, vifc=vifc) diff --git a/src/migration-scripts/policy/5-to-6 b/src/migration-scripts/policy/5-to-6 old mode 100755 new mode 100644 index 86287d578..acba0b4be --- a/src/migration-scripts/policy/5-to-6 +++ b/src/migration-scripts/policy/5-to-6 @@ -1,62 +1,42 @@ -#!/usr/bin/env python3 +# Copyright 2023-2024 VyOS maintainers and contributors <maintainers@vyos.io> # -# Copyright (C) 2023 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 program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, +# 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 General Public License for more details. +# 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 General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. # T5165: Migrate policy local-route rule <tag> destination|source -from sys import argv -from sys import exit - from vyos.configtree import ConfigTree -if len(argv) < 2: - print("Must specify file name!") - exit(1) - -file_name = argv[1] - -with open(file_name, 'r') as f: - config_file = f.read() - base4 = ['policy', 'local-route'] base6 = ['policy', 'local-route6'] -config = ConfigTree(config_file) - -if not config.exists(base4) and not config.exists(base6): - # Nothing to do - exit(0) - -# replace 'policy local-route{v6} rule <tag> destination|source <x.x.x.x>' -# => 'policy local-route{v6} rule <tag> destination|source address <x.x.x.x>' -for base in [base4, base6]: - if config.exists(base + ['rule']): - for rule in config.list_nodes(base + ['rule']): - dst_path = base + ['rule', rule, 'destination'] - src_path = base + ['rule', rule, 'source'] - # Destination - if config.exists(dst_path): - for dst_addr in config.return_values(dst_path): - config.set(dst_path + ['address'], value=dst_addr, replace=False) - # Source - if config.exists(src_path): - for src_addr in config.return_values(src_path): - config.set(src_path + ['address'], value=src_addr, replace=False) -try: - with open(file_name, 'w') as f: - f.write(config.to_string()) -except OSError as e: - print("Failed to save the modified config: {}".format(e)) - exit(1) +def migrate(config: ConfigTree) -> None: + if not config.exists(base4) and not config.exists(base6): + # Nothing to do + return + + # replace 'policy local-route{v6} rule <tag> destination|source <x.x.x.x>' + # => 'policy local-route{v6} rule <tag> destination|source address <x.x.x.x>' + for base in [base4, base6]: + if config.exists(base + ['rule']): + for rule in config.list_nodes(base + ['rule']): + dst_path = base + ['rule', rule, 'destination'] + src_path = base + ['rule', rule, 'source'] + # Destination + if config.exists(dst_path): + for dst_addr in config.return_values(dst_path): + config.set(dst_path + ['address'], value=dst_addr, replace=False) + # Source + if config.exists(src_path): + for src_addr in config.return_values(src_path): + config.set(src_path + ['address'], value=src_addr, replace=False) diff --git a/src/migration-scripts/policy/6-to-7 b/src/migration-scripts/policy/6-to-7 old mode 100755 new mode 100644 index cdefc6837..69aa703c5 --- a/src/migration-scripts/policy/6-to-7 +++ b/src/migration-scripts/policy/6-to-7 @@ -1,76 +1,56 @@ -#!/usr/bin/env python3 +# Copyright 2023-2024 VyOS maintainers and contributors <maintainers@vyos.io> # -# Copyright (C) 2023-2024 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 program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, +# 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 General Public License for more details. +# 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 General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. # T5729: Switch to valueless whenever is possible. # From # set policy [route | route6] ... rule <rule> log enable # set policy [route | route6] ... rule <rule> log disable # To # set policy [route | route6] ... rule <rule> log # Remove command if log=disable -from sys import argv -from sys import exit - from vyos.configtree import ConfigTree -if len(argv) < 2: - print("Must specify file name!") - exit(1) - -file_name = argv[1] - -with open(file_name, 'r') as f: - config_file = f.read() - base = ['policy'] -config = ConfigTree(config_file) - -if not config.exists(base): - # Nothing to do - exit(0) - -for family in ['route', 'route6']: - if config.exists(base + [family]): - - for policy_name in config.list_nodes(base + [family]): - if config.exists(base + [family, policy_name, 'rule']): - for rule in config.list_nodes(base + [family, policy_name, 'rule']): - # Log - if config.exists(base + [family, policy_name, 'rule', rule, 'log']): - log_value = config.return_value(base + [family, policy_name, 'rule', rule, 'log']) - config.delete(base + [family, policy_name, 'rule', rule, 'log']) - if log_value == 'enable': - config.set(base + [family, policy_name, 'rule', rule, 'log']) - # State - if config.exists(base + [family, policy_name, 'rule', rule, 'state']): - flag_enable = 'False' - for state in ['established', 'invalid', 'new', 'related']: - if config.exists(base + [family, policy_name, 'rule', rule, 'state', state]): - state_value = config.return_value(base + [family, policy_name, 'rule', rule, 'state', state]) - config.delete(base + [family, policy_name, 'rule', rule, 'state', state]) - if state_value == 'enable': - config.set(base + [family, policy_name, 'rule', rule, 'state'], value=state, replace=False) - flag_enable = 'True' - if flag_enable == 'False': - config.delete(base + [family, policy_name, 'rule', rule, 'state']) -try: - with open(file_name, 'w') as f: - f.write(config.to_string()) -except OSError as e: - print("Failed to save the modified config: {}".format(e)) - exit(1) +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + # Nothing to do + return + + for family in ['route', 'route6']: + if config.exists(base + [family]): + + for policy_name in config.list_nodes(base + [family]): + if config.exists(base + [family, policy_name, 'rule']): + for rule in config.list_nodes(base + [family, policy_name, 'rule']): + # Log + if config.exists(base + [family, policy_name, 'rule', rule, 'log']): + log_value = config.return_value(base + [family, policy_name, 'rule', rule, 'log']) + config.delete(base + [family, policy_name, 'rule', rule, 'log']) + if log_value == 'enable': + config.set(base + [family, policy_name, 'rule', rule, 'log']) + # State + if config.exists(base + [family, policy_name, 'rule', rule, 'state']): + flag_enable = 'False' + for state in ['established', 'invalid', 'new', 'related']: + if config.exists(base + [family, policy_name, 'rule', rule, 'state', state]): + state_value = config.return_value(base + [family, policy_name, 'rule', rule, 'state', state]) + config.delete(base + [family, policy_name, 'rule', rule, 'state', state]) + if state_value == 'enable': + config.set(base + [family, policy_name, 'rule', rule, 'state'], value=state, replace=False) + flag_enable = 'True' + if flag_enable == 'False': + config.delete(base + [family, policy_name, 'rule', rule, 'state']) diff --git a/src/migration-scripts/policy/7-to-8 b/src/migration-scripts/policy/7-to-8 old mode 100755 new mode 100644 index 73eece1a6..a887f37fe --- a/src/migration-scripts/policy/7-to-8 +++ b/src/migration-scripts/policy/7-to-8 @@ -1,56 +1,36 @@ -#!/usr/bin/env python3 +# Copyright 2023-2024 VyOS maintainers and contributors <maintainers@vyos.io> # -# Copyright (C) 2023 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 program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, +# 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 General Public License for more details. +# 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 General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. # T5834: Rename 'enable-default-log' to 'default-log' # From # set policy [route | route 6] <route> enable-default-log # To # set policy [route | route 6] <route> default-log -from sys import argv -from sys import exit - from vyos.configtree import ConfigTree -if len(argv) < 2: - print("Must specify file name!") - exit(1) - -file_name = argv[1] - -with open(file_name, 'r') as f: - config_file = f.read() - base = ['policy'] -config = ConfigTree(config_file) - -if not config.exists(base): - # Nothing to do - exit(0) -for family in ['route', 'route6']: - if config.exists(base + [family]): +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + # Nothing to do + return - for policy_name in config.list_nodes(base + [family]): - if config.exists(base + [family, policy_name, 'enable-default-log']): - config.rename(base + [family, policy_name, 'enable-default-log'], 'default-log') + for family in ['route', 'route6']: + if config.exists(base + [family]): -try: - with open(file_name, 'w') as f: - f.write(config.to_string()) -except OSError as e: - print("Failed to save the modified config: {}".format(e)) - exit(1) + for policy_name in config.list_nodes(base + [family]): + if config.exists(base + [family, policy_name, 'enable-default-log']): + config.rename(base + [family, policy_name, 'enable-default-log'], 'default-log') diff --git a/src/migration-scripts/pppoe-server/0-to-1 b/src/migration-scripts/pppoe-server/0-to-1 old mode 100755 new mode 100644 index 4d36f8545..8c9a24fbe --- a/src/migration-scripts/pppoe-server/0-to-1 +++ b/src/migration-scripts/pppoe-server/0-to-1 @@ -1,50 +1,33 @@ -#!/usr/bin/env python3 +# Copyright 2020-2024 VyOS maintainers and contributors <maintainers@vyos.io> # -# Copyright (C) 2020 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 program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, +# 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 General Public License for more details. +# 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 General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. # Convert "service pppoe-server authentication radius-server node key" # to: "service pppoe-server authentication radius-server node secret" -from sys import argv, exit from vyos.configtree import ConfigTree -if len(argv) < 2: - print("Must specify file name!") - exit(1) - -file_name = argv[1] - -with open(file_name, 'r') as f: - config_file = f.read() - -ctree = ConfigTree(config_file) base = ['service', 'pppoe-server', 'authentication', 'radius-server'] -if not ctree.exists(base): - # Nothing to do - exit(0) -else: +def migrate(ctree: ConfigTree) -> None: + if not ctree.exists(base): + # Nothing to do + return + nodes = ctree.list_nodes(base) for node in nodes: if ctree.exists(base + [node, 'key']): val = ctree.return_value(base + [node, 'key']) ctree.set(base + [node, 'secret'], value=val, replace=False) ctree.delete(base + [node, 'key']) - - try: - open(file_name,'w').write(ctree.to_string()) - except OSError as e: - print("Failed to save the modified config: {}".format(e)) - exit(1) diff --git a/src/migration-scripts/pppoe-server/1-to-2 b/src/migration-scripts/pppoe-server/1-to-2 old mode 100755 new mode 100644 index b266893c0..c9c968bff --- a/src/migration-scripts/pppoe-server/1-to-2 +++ b/src/migration-scripts/pppoe-server/1-to-2 @@ -1,58 +1,41 @@ -#!/usr/bin/env python3 +# Copyright 2020-2024 VyOS maintainers and contributors <maintainers@vyos.io> # -# Copyright (C) 2020-2024 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 program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, +# 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 General Public License for more details. +# 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 General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. # change mppe node to a leaf node with value prefer -from sys import argv, exit from vyos.configtree import ConfigTree -if len(argv) < 2: - print("Must specify file name!") - exit(1) - -file_name = argv[1] +base = ['service', 'pppoe-server'] -with open(file_name, 'r') as f: - config_file = f.read() +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + # Nothing to do + return -config = ConfigTree(config_file) -base = ['service', 'pppoe-server'] -if not config.exists(base): - # Nothing to do - exit(0) -else: mppe_base = base + ['ppp-options', 'mppe'] if config.exists(mppe_base): # get current values tmp = config.list_nodes(mppe_base) # drop node(s) first ... config.delete(mppe_base) print(tmp) # set new value based on preference if 'require' in tmp: config.set(mppe_base, value='require') elif 'prefer' in tmp: config.set(mppe_base, value='prefer') elif 'deny' in tmp: config.set(mppe_base, value='deny') - - try: - with open(file_name, 'w') as f: - f.write(config.to_string()) - except OSError as e: - print("Failed to save the modified config: {}".format(e)) - exit(1) diff --git a/src/migration-scripts/pppoe-server/2-to-3 b/src/migration-scripts/pppoe-server/2-to-3 old mode 100755 new mode 100644 index a7be060df..160cffdf8 --- a/src/migration-scripts/pppoe-server/2-to-3 +++ b/src/migration-scripts/pppoe-server/2-to-3 @@ -1,48 +1,31 @@ -#!/usr/bin/env python3 +# Copyright 2020-2024 VyOS maintainers and contributors <maintainers@vyos.io> # -# Copyright (C) 2020 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 program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, +# 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 General Public License for more details. +# 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 General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. # Convert "service pppoe-server interface ethX" to: "service pppoe-server interface ethX {}" -from sys import argv, exit from vyos.configtree import ConfigTree -if len(argv) < 2: - print("Must specify file name!") - exit(1) - -file_name = argv[1] - -with open(file_name, 'r') as f: - config_file = f.read() - -ctree = ConfigTree(config_file) cbase = ['service', 'pppoe-server','interface'] -if not ctree.exists(cbase): - exit(0) -else: +def migrate(ctree: ConfigTree) -> None: + if not ctree.exists(cbase): + return + nics = ctree.return_values(cbase) # convert leafNode to a tagNode ctree.set(cbase) ctree.set_tag(cbase) for nic in nics: ctree.set(cbase + [nic]) - - try: - open(file_name,'w').write(ctree.to_string()) - except OSError as e: - print("Failed to save the modified config: {}".format(e)) - exit(1) diff --git a/src/migration-scripts/pppoe-server/3-to-4 b/src/migration-scripts/pppoe-server/3-to-4 old mode 100755 new mode 100644 index 477ed6f22..29dd62201 --- a/src/migration-scripts/pppoe-server/3-to-4 +++ b/src/migration-scripts/pppoe-server/3-to-4 @@ -1,139 +1,121 @@ -#!/usr/bin/env python3 +# Copyright 2020-2024 VyOS maintainers and contributors <maintainers@vyos.io> # -# Copyright (C) 2020-2024 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 program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, +# 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 General Public License for more details. +# 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 General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. # - remove primary/secondary identifier from nameserver -from sys import argv, exit from vyos.configtree import ConfigTree -if len(argv) < 2: - print("Must specify file name!") - exit(1) - -file_name = argv[1] - -with open(file_name, 'r') as f: - config_file = f.read() - -config = ConfigTree(config_file) base = ['service', 'pppoe-server'] -if not config.exists(base): - # Nothing to do - exit(0) -else: + +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + # Nothing to do + return # Migrate IPv4 DNS servers dns_base = base + ['dns-servers'] if config.exists(dns_base): for server in ['server-1', 'server-2']: if config.exists(dns_base + [server]): dns = config.return_value(dns_base + [server]) config.set(base + ['name-server'], value=dns, replace=False) config.delete(dns_base) # Migrate IPv6 DNS servers dns_base = base + ['dnsv6-servers'] if config.exists(dns_base): for server in ['server-1', 'server-2', 'server-3']: if config.exists(dns_base + [server]): dns = config.return_value(dns_base + [server]) config.set(base + ['name-server'], value=dns, replace=False) config.delete(dns_base) # Migrate IPv4 WINS servers wins_base = base + ['wins-servers'] if config.exists(wins_base): for server in ['server-1', 'server-2']: if config.exists(wins_base + [server]): wins = config.return_value(wins_base + [server]) config.set(base + ['wins-server'], value=wins, replace=False) config.delete(wins_base) # Migrate radius-settings node to RADIUS and use this as base for the # later migration of the RADIUS servers - this will save a lot of code radius_settings = base + ['authentication', 'radius-settings'] if config.exists(radius_settings): config.rename(radius_settings, 'radius') # Migrate RADIUS dynamic author / change of authorisation server dae_old = base + ['authentication', 'radius', 'dae-server'] if config.exists(dae_old): config.rename(dae_old, 'dynamic-author') dae_new = base + ['authentication', 'radius', 'dynamic-author'] if config.exists(dae_new + ['ip-address']): config.rename(dae_new + ['ip-address'], 'server') if config.exists(dae_new + ['secret']): config.rename(dae_new + ['secret'], 'key') # Migrate RADIUS server radius_server = base + ['authentication', 'radius-server'] if config.exists(radius_server): new_base = base + ['authentication', 'radius', 'server'] config.set(new_base) config.set_tag(new_base) for server in config.list_nodes(radius_server): old_base = radius_server + [server] config.copy(old_base, new_base + [server]) # migrate key if config.exists(new_base + [server, 'secret']): config.rename(new_base + [server, 'secret'], 'key') # remove old req-limit node if config.exists(new_base + [server, 'req-limit']): config.delete(new_base + [server, 'req-limit']) config.delete(radius_server) # Migrate IPv6 prefixes ipv6_base = base + ['client-ipv6-pool'] if config.exists(ipv6_base + ['prefix']): prefix_old = config.return_values(ipv6_base + ['prefix']) # delete old prefix CLI nodes config.delete(ipv6_base + ['prefix']) # create ned prefix tag node config.set(ipv6_base + ['prefix']) config.set_tag(ipv6_base + ['prefix']) for p in prefix_old: prefix = p.split(',')[0] mask = p.split(',')[1] config.set(ipv6_base + ['prefix', prefix, 'mask'], value=mask) if config.exists(ipv6_base + ['delegate-prefix']): prefix_old = config.return_values(ipv6_base + ['delegate-prefix']) # delete old delegate prefix CLI nodes config.delete(ipv6_base + ['delegate-prefix']) # create ned delegation tag node config.set(ipv6_base + ['delegate']) config.set_tag(ipv6_base + ['delegate']) for p in prefix_old: prefix = p.split(',')[0] mask = p.split(',')[1] config.set(ipv6_base + ['delegate', prefix, 'delegation-prefix'], value=mask) - - try: - with open(file_name, 'w') as f: - f.write(config.to_string()) - except OSError as e: - print("Failed to save the modified config: {}".format(e)) - exit(1) diff --git a/src/migration-scripts/pppoe-server/4-to-5 b/src/migration-scripts/pppoe-server/4-to-5 old mode 100755 new mode 100644 index 5850db673..03fbfb247 --- a/src/migration-scripts/pppoe-server/4-to-5 +++ b/src/migration-scripts/pppoe-server/4-to-5 @@ -1,49 +1,30 @@ -#!/usr/bin/env python3 +# Copyright 2020-2024 VyOS maintainers and contributors <maintainers@vyos.io> # -# Copyright (C) 2020 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 program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, +# 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 General Public License for more details. +# 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 General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. # - rename local-ip to gateway-address from vyos.configtree import ConfigTree -from sys import argv -from sys import exit - -if len(argv) < 2: - print("Must specify file name!") - exit(1) -file_name = argv[1] +base_path = ['service', 'pppoe-server'] -with open(file_name, 'r') as f: - config_file = f.read() +def migrate(config: ConfigTree) -> None: + if not config.exists(base_path): + # Nothing to do + return -config = ConfigTree(config_file) -base_path = ['service', 'pppoe-server'] -if not config.exists(base_path): - # Nothing to do - exit(0) -else: config_gw = base_path + ['local-ip'] if config.exists(config_gw): config.rename(config_gw, 'gateway-address') config.delete(config_gw) - - try: - with open(file_name, 'w') as f: - f.write(config.to_string()) - except OSError as e: - print("Failed to save the modified config: {}".format(e)) - exit(1) - diff --git a/src/migration-scripts/pppoe-server/5-to-6 b/src/migration-scripts/pppoe-server/5-to-6 old mode 100755 new mode 100644 index e079ae684..13de8f8d2 --- a/src/migration-scripts/pppoe-server/5-to-6 +++ b/src/migration-scripts/pppoe-server/5-to-6 @@ -1,52 +1,33 @@ -#!/usr/bin/env python3 +# Copyright 2022-2024 VyOS maintainers and contributors <maintainers@vyos.io> # -# Copyright (C) 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 program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, +# 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 General Public License for more details. +# 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 General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. # - T4703: merge vlan-id and vlan-range to vlan CLI node from vyos.configtree import ConfigTree -from sys import argv -from sys import exit - -if len(argv) < 2: - print("Must specify file name!") - exit(1) - -file_name = argv[1] -with open(file_name, 'r') as f: - config_file = f.read() - -config = ConfigTree(config_file) base_path = ['service', 'pppoe-server', 'interface'] -if not config.exists(base_path): - # Nothing to do - exit(0) - -for interface in config.list_nodes(base_path): - for vlan in ['vlan-id', 'vlan-range']: - if config.exists(base_path + [interface, vlan]): - print(interface, vlan) - for tmp in config.return_values(base_path + [interface, vlan]): - config.set(base_path + [interface, 'vlan'], value=tmp, replace=False) - config.delete(base_path + [interface, vlan]) - -try: - with open(file_name, 'w') as f: - f.write(config.to_string()) -except OSError as e: - print(f'Failed to save the modified config: {e}') - exit(1) +def migrate(config: ConfigTree) -> None: + if not config.exists(base_path): + # Nothing to do + return + + for interface in config.list_nodes(base_path): + for vlan in ['vlan-id', 'vlan-range']: + if config.exists(base_path + [interface, vlan]): + print(interface, vlan) + for tmp in config.return_values(base_path + [interface, vlan]): + config.set(base_path + [interface, 'vlan'], value=tmp, replace=False) + config.delete(base_path + [interface, vlan]) diff --git a/src/migration-scripts/pppoe-server/6-to-7 b/src/migration-scripts/pppoe-server/6-to-7 old mode 100755 new mode 100644 index d51c1c9d8..79745a0c6 --- a/src/migration-scripts/pppoe-server/6-to-7 +++ b/src/migration-scripts/pppoe-server/6-to-7 @@ -1,117 +1,99 @@ -#!/usr/bin/env python3 +# Copyright 2023-2024 VyOS maintainers and contributors <maintainers@vyos.io> # -# Copyright (C) 2023-2024 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 program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, +# 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 General Public License for more details. +# 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 General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. # - move all pool to named pools # 'start-stop' migrate to namedpool 'default-range-pool' # 'subnet' migrate to namedpool 'default-subnet-pool' # 'default-subnet-pool' is the next pool for 'default-range-pool' # - There is only one gateway-address, take the first which is configured # - default-pool by migration. # 1. If authentication mode = 'local' then it is first named pool. # If there are not named pools, namedless pool will be default. # 2. If authentication mode = 'radius' then namedless pool will be default -from sys import argv -from sys import exit from vyos.configtree import ConfigTree from vyos.base import Warning -if len(argv) < 2: - print("Must specify file name!") - exit(1) - -file_name = argv[1] - -with open(file_name, 'r') as f: - config_file = f.read() - -config = ConfigTree(config_file) base = ['service', 'pppoe-server'] pool_base = base + ['client-ip-pool'] -if not config.exists(base): - exit(0) - -if not config.exists(pool_base): - exit(0) - -default_pool = '' -range_pool_name = 'default-range-pool' - -#Default nameless pools migrations -if config.exists(pool_base + ['start']) and config.exists(pool_base + ['stop']): - def is_legalrange(ip1: str, ip2: str, mask: str): - from ipaddress import IPv4Interface - interface1 = IPv4Interface(f'{ip1}/{mask}') - interface2 = IPv4Interface(f'{ip2}/{mask}') - return interface1.network.network_address == interface2.network.network_address and interface2.ip > interface1.ip - start_ip = config.return_value(pool_base + ['start']) - stop_ip = config.return_value(pool_base + ['stop']) - if is_legalrange(start_ip, stop_ip, '24'): - ip_range = f'{start_ip}-{stop_ip}' - config.set(pool_base + [range_pool_name, 'range'], value=ip_range, replace=False) +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + return + + if not config.exists(pool_base): + return + + default_pool = '' + range_pool_name = 'default-range-pool' + + #Default nameless pools migrations + if config.exists(pool_base + ['start']) and config.exists(pool_base + ['stop']): + def is_legalrange(ip1: str, ip2: str, mask: str): + from ipaddress import IPv4Interface + interface1 = IPv4Interface(f'{ip1}/{mask}') + interface2 = IPv4Interface(f'{ip2}/{mask}') + return interface1.network.network_address == interface2.network.network_address and interface2.ip > interface1.ip + + start_ip = config.return_value(pool_base + ['start']) + stop_ip = config.return_value(pool_base + ['stop']) + if is_legalrange(start_ip, stop_ip, '24'): + ip_range = f'{start_ip}-{stop_ip}' + config.set(pool_base + [range_pool_name, 'range'], value=ip_range, replace=False) + default_pool = range_pool_name + else: + Warning( + f'PPPoE client-ip-pool range start-ip:{start_ip} and stop-ip:{stop_ip} can not be migrated.') + config.delete(pool_base + ['start']) + config.delete(pool_base + ['stop']) + + if config.exists(pool_base + ['subnet']): default_pool = range_pool_name - else: - Warning( - f'PPPoE client-ip-pool range start-ip:{start_ip} and stop-ip:{stop_ip} can not be migrated.') - config.delete(pool_base + ['start']) - config.delete(pool_base + ['stop']) - -if config.exists(pool_base + ['subnet']): - default_pool = range_pool_name - for subnet in config.return_values(pool_base + ['subnet']): - config.set(pool_base + [range_pool_name, 'range'], value=subnet, replace=False) - config.delete(pool_base + ['subnet']) - -gateway = '' -if config.exists(base + ['gateway-address']): - gateway = config.return_value(base + ['gateway-address']) - -#named pool migration -namedpools_base = pool_base + ['name'] -if config.exists(namedpools_base): - if config.exists(base + ['authentication', 'mode']): - if config.return_value(base + ['authentication', 'mode']) == 'local': - if config.list_nodes(namedpools_base): - default_pool = config.list_nodes(namedpools_base)[0] - - for pool_name in config.list_nodes(namedpools_base): - pool_path = namedpools_base + [pool_name] - if config.exists(pool_path + ['subnet']): - subnet = config.return_value(pool_path + ['subnet']) - config.set(pool_base + [pool_name, 'range'], value=subnet, replace=False) - if config.exists(pool_path + ['next-pool']): - next_pool = config.return_value(pool_path + ['next-pool']) - config.set(pool_base + [pool_name, 'next-pool'], value=next_pool) - if not gateway: - if config.exists(pool_path + ['gateway-address']): - gateway = config.return_value(pool_path + ['gateway-address']) - - config.delete(namedpools_base) - -if gateway: - config.set(base + ['gateway-address'], value=gateway) -if default_pool: - config.set(base + ['default-pool'], value=default_pool) -# format as tag node -config.set_tag(pool_base) - -try: - with open(file_name, 'w') as f: - f.write(config.to_string()) -except OSError as e: - print("Failed to save the modified config: {}".format(e)) - exit(1) + for subnet in config.return_values(pool_base + ['subnet']): + config.set(pool_base + [range_pool_name, 'range'], value=subnet, replace=False) + config.delete(pool_base + ['subnet']) + + gateway = '' + if config.exists(base + ['gateway-address']): + gateway = config.return_value(base + ['gateway-address']) + + #named pool migration + namedpools_base = pool_base + ['name'] + if config.exists(namedpools_base): + if config.exists(base + ['authentication', 'mode']): + if config.return_value(base + ['authentication', 'mode']) == 'local': + if config.list_nodes(namedpools_base): + default_pool = config.list_nodes(namedpools_base)[0] + + for pool_name in config.list_nodes(namedpools_base): + pool_path = namedpools_base + [pool_name] + if config.exists(pool_path + ['subnet']): + subnet = config.return_value(pool_path + ['subnet']) + config.set(pool_base + [pool_name, 'range'], value=subnet, replace=False) + if config.exists(pool_path + ['next-pool']): + next_pool = config.return_value(pool_path + ['next-pool']) + config.set(pool_base + [pool_name, 'next-pool'], value=next_pool) + if not gateway: + if config.exists(pool_path + ['gateway-address']): + gateway = config.return_value(pool_path + ['gateway-address']) + + config.delete(namedpools_base) + + if gateway: + config.set(base + ['gateway-address'], value=gateway) + if default_pool: + config.set(base + ['default-pool'], value=default_pool) + # format as tag node + config.set_tag(pool_base) diff --git a/src/migration-scripts/pppoe-server/7-to-8 b/src/migration-scripts/pppoe-server/7-to-8 old mode 100755 new mode 100644 index 0381f0bf9..90e4fa053 --- a/src/migration-scripts/pppoe-server/7-to-8 +++ b/src/migration-scripts/pppoe-server/7-to-8 @@ -1,58 +1,40 @@ -#!/usr/bin/env python3 +# Copyright 2023-2024 VyOS maintainers and contributors <maintainers@vyos.io> # -# Copyright (C) 2023-2024 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 program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, +# 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 General Public License for more details. +# 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 General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. # Migrating to named ipv6 pools -from sys import argv -from sys import exit from vyos.configtree import ConfigTree -if len(argv) < 2: - print("Must specify file name!") - exit(1) +base = ['service', 'pppoe-server'] +pool_base = base + ['client-ipv6-pool'] -file_name = argv[1] +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + return -with open(file_name, 'r') as f: - config_file = f.read() + if not config.exists(pool_base): + return -config = ConfigTree(config_file) -base = ['service', 'pppoe-server'] -pool_base = base + ['client-ipv6-pool'] -if not config.exists(base): - exit(0) - -if not config.exists(pool_base): - exit(0) - -ipv6_pool_name = 'ipv6-pool' -config.copy(pool_base, pool_base + [ipv6_pool_name]) - -if config.exists(pool_base + ['prefix']): - config.delete(pool_base + ['prefix']) - config.set(base + ['default-ipv6-pool'], value=ipv6_pool_name) -if config.exists(pool_base + ['delegate']): - config.delete(pool_base + ['delegate']) - -# format as tag node -config.set_tag(pool_base) - -try: - with open(file_name, 'w') as f: - f.write(config.to_string()) -except OSError as e: - print("Failed to save the modified config: {}".format(e)) - exit(1) + ipv6_pool_name = 'ipv6-pool' + config.copy(pool_base, pool_base + [ipv6_pool_name]) + + if config.exists(pool_base + ['prefix']): + config.delete(pool_base + ['prefix']) + config.set(base + ['default-ipv6-pool'], value=ipv6_pool_name) + if config.exists(pool_base + ['delegate']): + config.delete(pool_base + ['delegate']) + + # format as tag node + config.set_tag(pool_base) diff --git a/src/migration-scripts/pppoe-server/8-to-9 b/src/migration-scripts/pppoe-server/8-to-9 old mode 100755 new mode 100644 index 4932a766f..e7e0aaa2c --- a/src/migration-scripts/pppoe-server/8-to-9 +++ b/src/migration-scripts/pppoe-server/8-to-9 @@ -1,66 +1,48 @@ -#!/usr/bin/env python3 +# Copyright 2024 VyOS maintainers and contributors <maintainers@vyos.io> # -# Copyright (C) 2024 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 program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, +# 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 General Public License for more details. +# 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 General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. # Change from 'ccp' to 'disable-ccp' in ppp-option section # Migration ipv6 options -from sys import argv -from sys import exit from vyos.configtree import ConfigTree -if len(argv) < 2: - print("Must specify file name!") - exit(1) - -file_name = argv[1] - -with open(file_name, 'r') as f: - config_file = f.read() - -config = ConfigTree(config_file) base = ['service', 'pppoe-server'] -if not config.exists(base): - exit(0) - -#CCP migration -if config.exists(base + ['ppp-options', 'ccp']): - config.delete(base + ['ppp-options', 'ccp']) -else: - config.set(base + ['ppp-options', 'disable-ccp']) - -#IPV6 options migrations -if config.exists(base + ['ppp-options','ipv6-peer-intf-id']): - intf_peer_id = config.return_value(base + ['ppp-options','ipv6-peer-intf-id']) - if intf_peer_id == 'ipv4': - intf_peer_id = 'ipv4-addr' - config.set(base + ['ppp-options','ipv6-peer-interface-id'], value=intf_peer_id, replace=True) - config.delete(base + ['ppp-options','ipv6-peer-intf-id']) - -if config.exists(base + ['ppp-options','ipv6-intf-id']): - intf_id = config.return_value(base + ['ppp-options','ipv6-intf-id']) - config.set(base + ['ppp-options','ipv6-interface-id'], value=intf_id, replace=True) - config.delete(base + ['ppp-options','ipv6-intf-id']) - -if config.exists(base + ['ppp-options','ipv6-accept-peer-intf-id']): - config.set(base + ['ppp-options','ipv6-accept-peer-interface-id']) - config.delete(base + ['ppp-options','ipv6-accept-peer-intf-id']) -try: - with open(file_name, 'w') as f: - f.write(config.to_string()) -except OSError as e: - print("Failed to save the modified config: {}".format(e)) - exit(1) +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + return + + #CCP migration + if config.exists(base + ['ppp-options', 'ccp']): + config.delete(base + ['ppp-options', 'ccp']) + else: + config.set(base + ['ppp-options', 'disable-ccp']) + + #IPV6 options migrations + if config.exists(base + ['ppp-options','ipv6-peer-intf-id']): + intf_peer_id = config.return_value(base + ['ppp-options','ipv6-peer-intf-id']) + if intf_peer_id == 'ipv4': + intf_peer_id = 'ipv4-addr' + config.set(base + ['ppp-options','ipv6-peer-interface-id'], value=intf_peer_id, replace=True) + config.delete(base + ['ppp-options','ipv6-peer-intf-id']) + + if config.exists(base + ['ppp-options','ipv6-intf-id']): + intf_id = config.return_value(base + ['ppp-options','ipv6-intf-id']) + config.set(base + ['ppp-options','ipv6-interface-id'], value=intf_id, replace=True) + config.delete(base + ['ppp-options','ipv6-intf-id']) + + if config.exists(base + ['ppp-options','ipv6-accept-peer-intf-id']): + config.set(base + ['ppp-options','ipv6-accept-peer-interface-id']) + config.delete(base + ['ppp-options','ipv6-accept-peer-intf-id']) diff --git a/src/migration-scripts/pppoe-server/9-to-10 b/src/migration-scripts/pppoe-server/9-to-10 old mode 100755 new mode 100644 index e0c782f04..d3475e8ff --- a/src/migration-scripts/pppoe-server/9-to-10 +++ b/src/migration-scripts/pppoe-server/9-to-10 @@ -1,56 +1,38 @@ -#!/usr/bin/env python3 +# Copyright 2024 VyOS maintainers and contributors <maintainers@vyos.io> # -# Copyright (C) 2024 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 program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, +# 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 General Public License for more details. +# 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 General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. # Migration of pado-delay options -from sys import argv -from sys import exit from vyos.configtree import ConfigTree -if len(argv) < 2: - print("Must specify file name!") - exit(1) - -file_name = argv[1] - -with open(file_name, 'r') as f: - config_file = f.read() - -config = ConfigTree(config_file) base = ['service', 'pppoe-server', 'pado-delay'] -if not config.exists(base): - exit(0) -pado_delay = {} -for delay in config.list_nodes(base): - sessions = config.return_value(base + [delay, 'sessions']) - pado_delay[delay] = sessions +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + return -# need to define delay for latest sessions -sorted_delays = dict(sorted(pado_delay.items(), key=lambda k_v: int(k_v[1]))) -last_delay = list(sorted_delays)[-1] + pado_delay = {} + for delay in config.list_nodes(base): + sessions = config.return_value(base + [delay, 'sessions']) + pado_delay[delay] = sessions -# Rename last delay -> disable -tmp = base + [last_delay] -if config.exists(tmp): - config.rename(tmp, 'disable') + # need to define delay for latest sessions + sorted_delays = dict(sorted(pado_delay.items(), key=lambda k_v: int(k_v[1]))) + last_delay = list(sorted_delays)[-1] -try: - with open(file_name, 'w') as f: - f.write(config.to_string()) -except OSError as e: - print("Failed to save the modified config: {}".format(e)) - exit(1) + # Rename last delay -> disable + tmp = base + [last_delay] + if config.exists(tmp): + config.rename(tmp, 'disable') diff --git a/src/migration-scripts/pptp/0-to-1 b/src/migration-scripts/pptp/0-to-1 old mode 100755 new mode 100644 index 1b7697c11..dd0b6f57e --- a/src/migration-scripts/pptp/0-to-1 +++ b/src/migration-scripts/pptp/0-to-1 @@ -1,59 +1,54 @@ -#!/usr/bin/env python3 +# Copyright 2018-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# 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 <http://www.gnu.org/licenses/>. # Unclutter PPTP VPN configuiration - move radius-server top level tag # nodes to a regular node which now also configures the radius source address # used when querying a radius server -import sys - from vyos.configtree import ConfigTree -if len(sys.argv) < 2: - print("Must specify file name!") - sys.exit(1) - -file_name = sys.argv[1] - -with open(file_name, 'r') as f: - config_file = f.read() +cfg_base = ['vpn', 'pptp', 'remote-access', 'authentication'] -config = ConfigTree(config_file) +def migrate(config: ConfigTree) -> None: + if not config.exists(cfg_base): + # Nothing to do + return -cfg_base = ['vpn', 'pptp', 'remote-access', 'authentication'] -if not config.exists(cfg_base): - # Nothing to do - sys.exit(0) -else: # Migrate "vpn pptp authentication radius-source-address" to new # "vpn pptp authentication radius source-address" if config.exists(cfg_base + ['radius-source-address']): address = config.return_value(cfg_base + ['radius-source-address']) # delete old configuration node config.delete(cfg_base + ['radius-source-address']) # write new configuration node config.set(cfg_base + ['radius', 'source-address'], value=address) # Migrate "vpn pptp authentication radius-server" tag node to new # "vpn pptp authentication radius server" tag node for server in config.list_nodes(cfg_base + ['radius-server']): base_server = cfg_base + ['radius-server', server] key = config.return_value(base_server + ['key']) # delete old configuration node config.delete(base_server) # write new configuration node config.set(cfg_base + ['radius', 'server', server, 'key'], value=key) # format as tag node config.set_tag(cfg_base + ['radius', 'server']) # delete top level tag node if config.exists(cfg_base + ['radius-server']): config.delete(cfg_base + ['radius-server']) - - try: - with open(file_name, 'w') as f: - f.write(config.to_string()) - except OSError as e: - print("Failed to save the modified config: {}".format(e)) - sys.exit(1) diff --git a/src/migration-scripts/pptp/1-to-2 b/src/migration-scripts/pptp/1-to-2 old mode 100755 new mode 100644 index 99624dceb..1e7601193 --- a/src/migration-scripts/pptp/1-to-2 +++ b/src/migration-scripts/pptp/1-to-2 @@ -1,71 +1,53 @@ -#!/usr/bin/env python3 +# Copyright 2020-2024 VyOS maintainers and contributors <maintainers@vyos.io> # -# Copyright (C) 2020 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 program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, +# 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 General Public License for more details. +# 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 General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. # - migrate dns-servers node to common name-servers # - remove radios req-limit node -from sys import argv, exit - from vyos.configtree import ConfigTree -if len(argv) < 2: - print("Must specify file name!") - exit(1) - -file_name = argv[1] +base = ['vpn', 'pptp', 'remote-access'] -with open(file_name, 'r') as f: - config_file = f.read() +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + # Nothing to do + return -config = ConfigTree(config_file) -base = ['vpn', 'pptp', 'remote-access'] -if not config.exists(base): - # Nothing to do - exit(0) -else: # Migrate IPv4 DNS servers dns_base = base + ['dns-servers'] if config.exists(dns_base): for server in ['server-1', 'server-2']: if config.exists(dns_base + [server]): dns = config.return_value(dns_base + [server]) config.set(base + ['name-server'], value=dns, replace=False) config.delete(dns_base) # Migrate IPv4 WINS servers wins_base = base + ['wins-servers'] if config.exists(wins_base): for server in ['server-1', 'server-2']: if config.exists(wins_base + [server]): wins = config.return_value(wins_base + [server]) config.set(base + ['wins-server'], value=wins, replace=False) config.delete(wins_base) # Remove RADIUS server req-limit node radius_base = base + ['authentication', 'radius'] if config.exists(radius_base): for server in config.list_nodes(radius_base + ['server']): if config.exists(radius_base + ['server', server, 'req-limit']): config.delete(radius_base + ['server', server, 'req-limit']) - - try: - with open(file_name, 'w') as f: - f.write(config.to_string()) - except OSError as e: - print("Failed to save the modified config: {}".format(e)) - exit(1) diff --git a/src/migration-scripts/pptp/2-to-3 b/src/migration-scripts/pptp/2-to-3 old mode 100755 new mode 100644 index 42c4dedf4..8b0d6d865 --- a/src/migration-scripts/pptp/2-to-3 +++ b/src/migration-scripts/pptp/2-to-3 @@ -1,73 +1,55 @@ -#!/usr/bin/env python3 +# Copyright 2023-2024 VyOS maintainers and contributors <maintainers@vyos.io> # -# Copyright (C) 2023-2024 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 program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, +# 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 General Public License for more details. +# 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 General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. # - move all pool to named pools # 'start-stop' migrate to namedpool 'default-range-pool' # 'default-subnet-pool' is the next pool for 'default-range-pool' -from sys import argv -from sys import exit from vyos.configtree import ConfigTree from vyos.base import Warning -if len(argv) < 2: - print("Must specify file name!") - exit(1) - -file_name = argv[1] - -with open(file_name, 'r') as f: - config_file = f.read() - -config = ConfigTree(config_file) base = ['vpn', 'pptp', 'remote-access'] pool_base = base + ['client-ip-pool'] -if not config.exists(base): - exit(0) - -if not config.exists(pool_base): - exit(0) - -range_pool_name = 'default-range-pool' - -if config.exists(pool_base + ['start']) and config.exists(pool_base + ['stop']): - def is_legalrange(ip1: str, ip2: str, mask: str): - from ipaddress import IPv4Interface - interface1 = IPv4Interface(f'{ip1}/{mask}') - interface2 = IPv4Interface(f'{ip2}/{mask}') - return interface1.network.network_address == interface2.network.network_address and interface2.ip > interface1.ip - - start_ip = config.return_value(pool_base + ['start']) - stop_ip = config.return_value(pool_base + ['stop']) - if is_legalrange(start_ip, stop_ip, '24'): - ip_range = f'{start_ip}-{stop_ip}' - config.set(pool_base + [range_pool_name, 'range'], value=ip_range, replace=False) - config.set(base + ['default-pool'], value=range_pool_name) - else: - Warning( - f'PPTP client-ip-pool range start-ip:{start_ip} and stop-ip:{stop_ip} can not be migrated.') - - config.delete(pool_base + ['start']) - config.delete(pool_base + ['stop']) -# format as tag node -config.set_tag(pool_base) -try: - with open(file_name, 'w') as f: - f.write(config.to_string()) -except OSError as e: - print("Failed to save the modified config: {}".format(e)) - exit(1) +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + return + + if not config.exists(pool_base): + return + + range_pool_name = 'default-range-pool' + + if config.exists(pool_base + ['start']) and config.exists(pool_base + ['stop']): + def is_legalrange(ip1: str, ip2: str, mask: str): + from ipaddress import IPv4Interface + interface1 = IPv4Interface(f'{ip1}/{mask}') + interface2 = IPv4Interface(f'{ip2}/{mask}') + return interface1.network.network_address == interface2.network.network_address and interface2.ip > interface1.ip + + start_ip = config.return_value(pool_base + ['start']) + stop_ip = config.return_value(pool_base + ['stop']) + if is_legalrange(start_ip, stop_ip, '24'): + ip_range = f'{start_ip}-{stop_ip}' + config.set(pool_base + [range_pool_name, 'range'], value=ip_range, replace=False) + config.set(base + ['default-pool'], value=range_pool_name) + else: + Warning( + f'PPTP client-ip-pool range start-ip:{start_ip} and stop-ip:{stop_ip} can not be migrated.') + + config.delete(pool_base + ['start']) + config.delete(pool_base + ['stop']) + # format as tag node + config.set_tag(pool_base) diff --git a/src/migration-scripts/pptp/3-to-4 b/src/migration-scripts/pptp/3-to-4 old mode 100755 new mode 100644 index ebd343028..2dabd8475 --- a/src/migration-scripts/pptp/3-to-4 +++ b/src/migration-scripts/pptp/3-to-4 @@ -1,48 +1,29 @@ -#!/usr/bin/env python3 +# Copyright 2024 VyOS maintainers and contributors <maintainers@vyos.io> # -# Copyright (C) 2024 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 program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, +# 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 General Public License for more details. +# 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 General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. # - Move 'mppe' from 'authentication' node to 'ppp-options' -from sys import argv -from sys import exit from vyos.configtree import ConfigTree -if len(argv) < 2: - print("Must specify file name!") - exit(1) - -file_name = argv[1] - -with open(file_name, 'r') as f: - config_file = f.read() - -config = ConfigTree(config_file) base = ['vpn', 'pptp', 'remote-access'] -if not config.exists(base): - exit(0) - -if config.exists(base + ['authentication','mppe']): - mppe = config.return_value(base + ['authentication','mppe']) - config.set(base + ['ppp-options', 'mppe'], value=mppe, replace=True) - config.delete(base + ['authentication','mppe']) +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + return -try: - with open(file_name, 'w') as f: - f.write(config.to_string()) -except OSError as e: - print("Failed to save the modified config: {}".format(e)) - exit(1) + if config.exists(base + ['authentication','mppe']): + mppe = config.return_value(base + ['authentication','mppe']) + config.set(base + ['ppp-options', 'mppe'], value=mppe, replace=True) + config.delete(base + ['authentication','mppe']) diff --git a/src/migration-scripts/pptp/4-to-5 b/src/migration-scripts/pptp/4-to-5 old mode 100755 new mode 100644 index 83632b6d8..c906f58c4 --- a/src/migration-scripts/pptp/4-to-5 +++ b/src/migration-scripts/pptp/4-to-5 @@ -1,63 +1,43 @@ -#!/usr/bin/env python3 +# Copyright 2024 VyOS maintainers and contributors <maintainers@vyos.io> # -# Copyright (C) 2024 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 program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, +# 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 General Public License for more details. +# 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 General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. # - Move 'require' from 'protocols' in 'authentication' node # - Migrate to new default values in radius timeout and acct-timeout -from sys import argv -from sys import exit from vyos.configtree import ConfigTree -if len(argv) < 2: - print("Must specify file name!") - exit(1) - -file_name = argv[1] - -with open(file_name, 'r') as f: - config_file = f.read() - -config = ConfigTree(config_file) base = ['vpn', 'pptp', 'remote-access'] -if not config.exists(base): - exit(0) - -#migrate require to protocols -require_path = base + ['authentication', 'require'] -if config.exists(require_path): - protocols = list(config.return_values(require_path)) - for protocol in protocols: - config.set(base + ['authentication', 'protocols'], value=protocol, - replace=False) - config.delete(require_path) -else: - config.set(base + ['authentication', 'protocols'], value='mschap-v2') - -radius_path = base + ['authentication', 'radius'] -if config.exists(radius_path): - if not config.exists(radius_path + ['timeout']): - config.set(radius_path + ['timeout'], value=3) - if not config.exists(radius_path + ['acct-timeout']): - config.set(radius_path + ['acct-timeout'], value=3) - - -try: - with open(file_name, 'w') as f: - f.write(config.to_string()) -except OSError as e: - print("Failed to save the modified config: {}".format(e)) - exit(1) +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + return + + #migrate require to protocols + require_path = base + ['authentication', 'require'] + if config.exists(require_path): + protocols = list(config.return_values(require_path)) + for protocol in protocols: + config.set(base + ['authentication', 'protocols'], value=protocol, + replace=False) + config.delete(require_path) + else: + config.set(base + ['authentication', 'protocols'], value='mschap-v2') + + radius_path = base + ['authentication', 'radius'] + if config.exists(radius_path): + if not config.exists(radius_path + ['timeout']): + config.set(radius_path + ['timeout'], value=3) + if not config.exists(radius_path + ['acct-timeout']): + config.set(radius_path + ['acct-timeout'], value=3) diff --git a/src/migration-scripts/qos/1-to-2 b/src/migration-scripts/qos/1-to-2 old mode 100755 new mode 100644 index 666811e5a..c43d8fa47 --- a/src/migration-scripts/qos/1-to-2 +++ b/src/migration-scripts/qos/1-to-2 @@ -1,194 +1,168 @@ -#!/usr/bin/env python3 +# Copyright 2022-2024 VyOS maintainers and contributors <maintainers@vyos.io> # -# Copyright (C) 2022-2024 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 program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, +# 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 General Public License for more details. +# 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 General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. - -from sys import argv,exit +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. from vyos.base import Warning from vyos.configtree import ConfigTree from vyos.utils.file import read_file def bandwidth_percent_to_val(interface, percent) -> int: speed = read_file(f'/sys/class/net/{interface}/speed') if not speed.isnumeric(): Warning('Interface speed cannot be determined (assuming 10 Mbit/s)') speed = 10 speed = int(speed) *1000000 # convert to MBit/s return speed * int(percent) // 100 # integer division -if len(argv) < 2: - print("Must specify file name!") - exit(1) - -file_name = argv[1] - -with open(file_name, 'r') as f: - config_file = f.read() - -base = ['traffic-policy'] -config = ConfigTree(config_file) - def delete_orphaned_interface_policy(config, iftype, ifname, vif=None, vifs=None, vifc=None): """Delete unexpected traffic-policy on interfaces in cases when policy does not exist but inreface has a policy configuration Example T5941: set interfaces bonding bond0 vif 995 traffic-policy """ if_path = ['interfaces', iftype, ifname] if vif: if_path += ['vif', vif] elif vifs: if_path += ['vif-s', vifs] if vifc: if_path += ['vif-c', vifc] if not config.exists(if_path + ['traffic-policy']): return config.delete(if_path + ['traffic-policy']) -if not config.exists(base): - # Delete orphaned nodes on interfaces T5941 - for iftype in config.list_nodes(['interfaces']): - for ifname in config.list_nodes(['interfaces', iftype]): - delete_orphaned_interface_policy(config, iftype, ifname) - - if config.exists(['interfaces', iftype, ifname, 'vif']): - for vif in config.list_nodes(['interfaces', iftype, ifname, 'vif']): - delete_orphaned_interface_policy(config, iftype, ifname, vif=vif) - - if config.exists(['interfaces', iftype, ifname, 'vif-s']): - for vifs in config.list_nodes(['interfaces', iftype, ifname, 'vif-s']): - delete_orphaned_interface_policy(config, iftype, ifname, vifs=vifs) - - if config.exists(['interfaces', iftype, ifname, 'vif-s', vifs, 'vif-c']): - for vifc in config.list_nodes(['interfaces', iftype, ifname, 'vif-s', vifs, 'vif-c']): - delete_orphaned_interface_policy(config, iftype, ifname, vifs=vifs, vifc=vifc) - - try: - with open(file_name, 'w') as f: - f.write(config.to_string()) - except OSError as e: - print("Failed to save the modified config: {}".format(e)) - exit(1) - - # Nothing to do - exit(0) - -iface_config = {} - -if config.exists(['interfaces']): - def get_qos(config, interface, interface_base): - if config.exists(interface_base): - tmp = { interface : {} } - if config.exists(interface_base + ['in']): - tmp[interface]['ingress'] = config.return_value(interface_base + ['in']) - if config.exists(interface_base + ['out']): - tmp[interface]['egress'] = config.return_value(interface_base + ['out']) - config.delete(interface_base) - return tmp - return None - - # Migrate "interface ethernet eth0 traffic-policy in|out" to "qos interface eth0 ingress|egress" - for type in config.list_nodes(['interfaces']): - for interface in config.list_nodes(['interfaces', type]): - interface_base = ['interfaces', type, interface, 'traffic-policy'] - tmp = get_qos(config, interface, interface_base) - if tmp: iface_config.update(tmp) - - vif_path = ['interfaces', type, interface, 'vif'] - if config.exists(vif_path): - for vif in config.list_nodes(vif_path): - vif_interface_base = vif_path + [vif, 'traffic-policy'] - ifname = f'{interface}.{vif}' - tmp = get_qos(config, ifname, vif_interface_base) - if tmp: iface_config.update(tmp) - - vif_s_path = ['interfaces', type, interface, 'vif-s'] - if config.exists(vif_s_path): - for vif_s in config.list_nodes(vif_s_path): - vif_s_interface_base = vif_s_path + [vif_s, 'traffic-policy'] - ifname = f'{interface}.{vif_s}' - tmp = get_qos(config, ifname, vif_s_interface_base) - if tmp: iface_config.update(tmp) - - # vif-c interfaces MUST be migrated before their parent vif-s - # interface as the migrate_*() functions delete the path! - vif_c_path = ['interfaces', type, interface, 'vif-s', vif_s, 'vif-c'] - if config.exists(vif_c_path): - for vif_c in config.list_nodes(vif_c_path): - vif_c_interface_base = vif_c_path + [vif_c, 'traffic-policy'] - ifname = f'{interface}.{vif_s}.{vif_c}' - tmp = get_qos(config, ifname, vif_s_interface_base) - if tmp: iface_config.update(tmp) - - -# Now we have the information which interface uses which QoS policy. -# Interface binding will be moved to the qos CLi tree -config.set(['qos']) -config.copy(base, ['qos', 'policy']) -config.delete(base) - -# Now map the interface policy binding to the new CLI syntax -if len(iface_config): - config.set(['qos', 'interface']) - config.set_tag(['qos', 'interface']) - -for interface, interface_config in iface_config.items(): - config.set(['qos', 'interface', interface]) - config.set_tag(['qos', 'interface', interface]) - if 'ingress' in interface_config: - config.set(['qos', 'interface', interface, 'ingress'], value=interface_config['ingress']) - if 'egress' in interface_config: - config.set(['qos', 'interface', interface, 'egress'], value=interface_config['egress']) - -# Remove "burst" CLI node from network emulator -netem_base = ['qos', 'policy', 'network-emulator'] -if config.exists(netem_base): - for policy_name in config.list_nodes(netem_base): - if config.exists(netem_base + [policy_name, 'burst']): - config.delete(netem_base + [policy_name, 'burst']) - -# Change bandwidth unit MBit -> mbit as tc only supports mbit -base = ['qos', 'policy'] -if config.exists(base): - for policy_type in config.list_nodes(base): - for policy in config.list_nodes(base + [policy_type]): - policy_base = base + [policy_type, policy] - if config.exists(policy_base + ['bandwidth']): - tmp = config.return_value(policy_base + ['bandwidth']) - config.set(policy_base + ['bandwidth'], value=tmp.lower()) - - if config.exists(policy_base + ['class']): - for cls in config.list_nodes(policy_base + ['class']): - cls_base = policy_base + ['class', cls] - if config.exists(cls_base + ['bandwidth']): - tmp = config.return_value(cls_base + ['bandwidth']) - config.set(cls_base + ['bandwidth'], value=tmp.lower()) - - if config.exists(policy_base + ['default', 'bandwidth']): +def migrate(config: ConfigTree) -> None: + base = ['traffic-policy'] + + if not config.exists(base): + # Delete orphaned nodes on interfaces T5941 + for iftype in config.list_nodes(['interfaces']): + for ifname in config.list_nodes(['interfaces', iftype]): + delete_orphaned_interface_policy(config, iftype, ifname) + + if config.exists(['interfaces', iftype, ifname, 'vif']): + for vif in config.list_nodes(['interfaces', iftype, ifname, 'vif']): + delete_orphaned_interface_policy(config, iftype, ifname, vif=vif) + + if config.exists(['interfaces', iftype, ifname, 'vif-s']): + for vifs in config.list_nodes(['interfaces', iftype, ifname, 'vif-s']): + delete_orphaned_interface_policy(config, iftype, ifname, vifs=vifs) + + if config.exists(['interfaces', iftype, ifname, 'vif-s', vifs, 'vif-c']): + for vifc in config.list_nodes(['interfaces', iftype, ifname, 'vif-s', vifs, 'vif-c']): + delete_orphaned_interface_policy(config, iftype, ifname, vifs=vifs, vifc=vifc) + + # Nothing to do + return + + iface_config = {} + + if config.exists(['interfaces']): + def get_qos(config, interface, interface_base): + if config.exists(interface_base): + tmp = { interface : {} } + if config.exists(interface_base + ['in']): + tmp[interface]['ingress'] = config.return_value(interface_base + ['in']) + if config.exists(interface_base + ['out']): + tmp[interface]['egress'] = config.return_value(interface_base + ['out']) + config.delete(interface_base) + return tmp + return None + + # Migrate "interface ethernet eth0 traffic-policy in|out" to "qos interface eth0 ingress|egress" + for type in config.list_nodes(['interfaces']): + for interface in config.list_nodes(['interfaces', type]): + interface_base = ['interfaces', type, interface, 'traffic-policy'] + tmp = get_qos(config, interface, interface_base) + if tmp: iface_config.update(tmp) + + vif_path = ['interfaces', type, interface, 'vif'] + if config.exists(vif_path): + for vif in config.list_nodes(vif_path): + vif_interface_base = vif_path + [vif, 'traffic-policy'] + ifname = f'{interface}.{vif}' + tmp = get_qos(config, ifname, vif_interface_base) + if tmp: iface_config.update(tmp) + + vif_s_path = ['interfaces', type, interface, 'vif-s'] + if config.exists(vif_s_path): + for vif_s in config.list_nodes(vif_s_path): + vif_s_interface_base = vif_s_path + [vif_s, 'traffic-policy'] + ifname = f'{interface}.{vif_s}' + tmp = get_qos(config, ifname, vif_s_interface_base) + if tmp: iface_config.update(tmp) + + # vif-c interfaces MUST be migrated before their parent vif-s + # interface as the migrate_*() functions delete the path! + vif_c_path = ['interfaces', type, interface, 'vif-s', vif_s, 'vif-c'] + if config.exists(vif_c_path): + for vif_c in config.list_nodes(vif_c_path): + vif_c_interface_base = vif_c_path + [vif_c, 'traffic-policy'] + ifname = f'{interface}.{vif_s}.{vif_c}' + tmp = get_qos(config, ifname, vif_s_interface_base) + if tmp: iface_config.update(tmp) + + + # Now we have the information which interface uses which QoS policy. + # Interface binding will be moved to the qos CLi tree + config.set(['qos']) + config.copy(base, ['qos', 'policy']) + config.delete(base) + + # Now map the interface policy binding to the new CLI syntax + if len(iface_config): + config.set(['qos', 'interface']) + config.set_tag(['qos', 'interface']) + + for interface, interface_config in iface_config.items(): + config.set(['qos', 'interface', interface]) + config.set_tag(['qos', 'interface', interface]) + if 'ingress' in interface_config: + config.set(['qos', 'interface', interface, 'ingress'], value=interface_config['ingress']) + if 'egress' in interface_config: + config.set(['qos', 'interface', interface, 'egress'], value=interface_config['egress']) + + # Remove "burst" CLI node from network emulator + netem_base = ['qos', 'policy', 'network-emulator'] + if config.exists(netem_base): + for policy_name in config.list_nodes(netem_base): + if config.exists(netem_base + [policy_name, 'burst']): + config.delete(netem_base + [policy_name, 'burst']) + + # Change bandwidth unit MBit -> mbit as tc only supports mbit + base = ['qos', 'policy'] + if config.exists(base): + for policy_type in config.list_nodes(base): + for policy in config.list_nodes(base + [policy_type]): + policy_base = base + [policy_type, policy] + if config.exists(policy_base + ['bandwidth']): + tmp = config.return_value(policy_base + ['bandwidth']) + config.set(policy_base + ['bandwidth'], value=tmp.lower()) + + if config.exists(policy_base + ['class']): + for cls in config.list_nodes(policy_base + ['class']): + cls_base = policy_base + ['class', cls] + if config.exists(cls_base + ['bandwidth']): + tmp = config.return_value(cls_base + ['bandwidth']) + config.set(cls_base + ['bandwidth'], value=tmp.lower()) + if config.exists(policy_base + ['default', 'bandwidth']): - tmp = config.return_value(policy_base + ['default', 'bandwidth']) - config.set(policy_base + ['default', 'bandwidth'], value=tmp.lower()) - -try: - with open(file_name, 'w') as f: - f.write(config.to_string()) -except OSError as e: - print("Failed to save the modified config: {}".format(e)) - exit(1) + if config.exists(policy_base + ['default', 'bandwidth']): + tmp = config.return_value(policy_base + ['default', 'bandwidth']) + config.set(policy_base + ['default', 'bandwidth'], value=tmp.lower()) diff --git a/src/migration-scripts/quagga/10-to-11 b/src/migration-scripts/quagga/10-to-11 old mode 100755 new mode 100644 index 0ed4f5df6..15dbbb193 --- a/src/migration-scripts/quagga/10-to-11 +++ b/src/migration-scripts/quagga/10-to-11 @@ -1,51 +1,31 @@ -#!/usr/bin/env python3 +# Copyright 2023-2024 VyOS maintainers and contributors <maintainers@vyos.io> # -# Copyright (C) 2023 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 program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, +# 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 General Public License for more details. +# 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 General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. # T5150: Rework CLI definitions to apply route-maps between routing daemons # and zebra/kernel -from sys import argv -from sys import exit - from vyos.configtree import ConfigTree -if len(argv) < 2: - print("Must specify file name!") - exit(1) - -file_name = argv[1] - -with open(file_name, 'r') as f: - config_file = f.read() - -config = ConfigTree(config_file) - static_base = ['protocols', 'static'] -# Check if static routes are configured - if so, migrate the CLI node -if config.exists(static_base): - if config.exists(static_base + ['route-map']): - tmp = config.return_value(static_base + ['route-map']) - config.set(['system', 'ip', 'protocol', 'static', 'route-map'], value=tmp) - config.set_tag(['system', 'ip', 'protocol']) - config.delete(static_base + ['route-map']) +def migrate(config: ConfigTree) -> None: + # Check if static routes are configured - if so, migrate the CLI node + if config.exists(static_base): + if config.exists(static_base + ['route-map']): + tmp = config.return_value(static_base + ['route-map']) -try: - with open(file_name, 'w') as f: - f.write(config.to_string()) -except OSError as e: - print(f'Failed to save the modified config: {e}') - exit(1) + config.set(['system', 'ip', 'protocol', 'static', 'route-map'], value=tmp) + config.set_tag(['system', 'ip', 'protocol']) + config.delete(static_base + ['route-map']) diff --git a/src/migration-scripts/quagga/2-to-3 b/src/migration-scripts/quagga/2-to-3 old mode 100755 new mode 100644 index 96b56da70..d62c387ba --- a/src/migration-scripts/quagga/2-to-3 +++ b/src/migration-scripts/quagga/2-to-3 @@ -1,203 +1,181 @@ -#!/usr/bin/env python3 +# Copyright 2018-2024 VyOS maintainers and contributors <maintainers@vyos.io> # -# 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 program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, +# 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 General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. -# +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. # - -import sys +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. from vyos.configtree import ConfigTree -if len(sys.argv) < 2: - print("Must specify file name!") - sys.exit(1) - -file_name = sys.argv[1] - -with open(file_name, 'r') as f: - config_file = f.read() - -config = ConfigTree(config_file) - def migrate_neighbor(config, neighbor_path, neighbor): if config.exists(neighbor_path): neighbors = config.list_nodes(neighbor_path) for neighbor in neighbors: # Move the valueless options: as-override, next-hop-self, route-reflector-client, route-server-client, # remove-private-as for valueless_option in ['as-override', 'nexthop-self', 'route-reflector-client', 'route-server-client', 'remove-private-as']: if config.exists(neighbor_path + [neighbor, valueless_option]): config.set(neighbor_path + [neighbor] + af_path + [valueless_option]) config.delete(neighbor_path + [neighbor, valueless_option]) # Move filter options: distribute-list, filter-list, prefix-list, and route-map # They share the same syntax inside so we can group them for filter_type in ['distribute-list', 'filter-list', 'prefix-list', 'route-map']: if config.exists(neighbor_path + [neighbor, filter_type]): for filter_dir in ['import', 'export']: if config.exists(neighbor_path + [neighbor, filter_type, filter_dir]): filter_name = config.return_value(neighbor_path + [neighbor, filter_type, filter_dir]) config.set(neighbor_path + [neighbor] + af_path + [filter_type, filter_dir], value=filter_name) config.delete(neighbor_path + [neighbor, filter_type]) # Move simple leaf node options: maximum-prefix, unsuppress-map, weight for leaf_option in ['maximum-prefix', 'unsuppress-map', 'weight']: if config.exists(neighbor_path + [neighbor, leaf_option]): if config.exists(neighbor_path + [neighbor, leaf_option]): leaf_opt_value = config.return_value(neighbor_path + [neighbor, leaf_option]) config.set(neighbor_path + [neighbor] + af_path + [leaf_option], value=leaf_opt_value) config.delete(neighbor_path + [neighbor, leaf_option]) # The rest is special cases, for better or worse # Move allowas-in if config.exists(neighbor_path + [neighbor, 'allowas-in']): if config.exists(neighbor_path + [neighbor, 'allowas-in', 'number']): allowas_in = config.return_value(neighbor_path + [neighbor, 'allowas-in', 'number']) config.set(neighbor_path + [neighbor] + af_path + ['allowas-in', 'number'], value=allowas_in) config.delete(neighbor_path + [neighbor, 'allowas-in']) # Move attribute-unchanged options if config.exists(neighbor_path + [neighbor, 'attribute-unchanged']): for attr in ['as-path', 'med', 'next-hop']: if config.exists(neighbor_path + [neighbor, 'attribute-unchanged', attr]): config.set(neighbor_path + [neighbor] + af_path + ['attribute-unchanged', attr]) config.delete(neighbor_path + [neighbor, 'attribute-unchanged', attr]) config.delete(neighbor_path + [neighbor, 'attribute-unchanged']) # Move capability options if config.exists(neighbor_path + [neighbor, 'capability']): # "capability dynamic" is a peer-global option, we only migrate ORF if config.exists(neighbor_path + [neighbor, 'capability', 'orf']): if config.exists(neighbor_path + [neighbor, 'capability', 'orf', 'prefix-list']): for orf in ['send', 'receive']: if config.exists(neighbor_path + [neighbor, 'capability', 'orf', 'prefix-list', orf]): config.set(neighbor_path + [neighbor] + af_path + ['capability', 'orf', 'prefix-list', orf]) config.delete(neighbor_path + [neighbor, 'capability', 'orf', 'prefix-list', orf]) config.delete(neighbor_path + [neighbor, 'capability', 'orf', 'prefix-list']) config.delete(neighbor_path + [neighbor, 'capability', 'orf']) # Move default-originate if config.exists(neighbor_path + [neighbor, 'default-originate']): if config.exists(neighbor_path + [neighbor, 'default-originate', 'route-map']): route_map = config.return_value(neighbor_path + [neighbor, 'default-originate', 'route-map']) config.set(neighbor_path + [neighbor] + af_path + ['default-originate', 'route-map'], value=route_map) else: # Empty default-originate node is meaningful so we re-create it config.set(neighbor_path + [neighbor] + af_path + ['default-originate']) config.delete(neighbor_path + [neighbor, 'default-originate']) # Move soft-reconfiguration if config.exists(neighbor_path + [neighbor, 'soft-reconfiguration']): if config.exists(neighbor_path + [neighbor, 'soft-reconfiguration', 'inbound']): config.set(neighbor_path + [neighbor] + af_path + ['soft-reconfiguration', 'inbound']) # Empty soft-reconfiguration is meaningless, so we just remove it config.delete(neighbor_path + [neighbor, 'soft-reconfiguration']) # Move disable-send-community if config.exists(neighbor_path + [neighbor, 'disable-send-community']): for comm_type in ['standard', 'extended']: if config.exists(neighbor_path + [neighbor, 'disable-send-community', comm_type]): config.set(neighbor_path + [neighbor] + af_path + ['disable-send-community', comm_type]) config.delete(neighbor_path + [neighbor, 'disable-send-community', comm_type]) config.delete(neighbor_path + [neighbor, 'disable-send-community']) -if not config.exists(['protocols', 'bgp']): - # Nothing to do - sys.exit(0) -else: +def migrate(config: ConfigTree) -> None: + if not config.exists(['protocols', 'bgp']): + # Nothing to do + return + # Just to avoid writing it so many times af_path = ['address-family', 'ipv4-unicast'] # Check if BGP is actually configured and obtain the ASN asn_list = config.list_nodes(['protocols', 'bgp']) if asn_list: # There's always just one BGP node, if any asn = asn_list[0] bgp_path = ['protocols', 'bgp', asn] else: # There's actually no BGP, just its empty shell - sys.exit(0) + return ## Move global IPv4-specific BGP options to "address-family ipv4-unicast" # Move networks network_path = ['protocols', 'bgp', asn, 'network'] if config.exists(network_path): config.set(bgp_path + af_path + ['network']) config.set_tag(bgp_path + af_path + ['network']) networks = config.list_nodes(network_path) for network in networks: config.set(bgp_path + af_path + ['network', network]) if config.exists(network_path + [network, 'route-map']): route_map = config.return_value(network_path + [network, 'route-map']) config.set(bgp_path + af_path + ['network', network, 'route-map'], value=route_map) config.delete(network_path) # Move aggregate-address statements aggregate_path = ['protocols', 'bgp', asn, 'aggregate-address'] if config.exists(aggregate_path): config.set(bgp_path + af_path + ['aggregate-address']) config.set_tag(bgp_path + af_path + ['aggregate-address']) aggregates = config.list_nodes(aggregate_path) for aggregate in aggregates: config.set(bgp_path + af_path + ['aggregate-address', aggregate]) if config.exists(aggregate_path + [aggregate, 'as-set']): config.set(bgp_path + af_path + ['aggregate-address', aggregate, 'as-set']) if config.exists(aggregate_path + [aggregate, 'summary-only']): config.set(bgp_path + af_path + ['aggregate-address', aggregate, 'summary-only']) config.delete(aggregate_path) ## Migrate neighbor options neighbor_path = ['protocols', 'bgp', asn, 'neighbor'] if config.exists(neighbor_path): neighbors = config.list_nodes(neighbor_path) for neighbor in neighbors: migrate_neighbor(config, neighbor_path, neighbor) peer_group_path = ['protocols', 'bgp', asn, 'peer-group'] if config.exists(peer_group_path): peer_groups = config.list_nodes(peer_group_path) for peer_group in peer_groups: migrate_neighbor(config, peer_group_path, peer_group) ## Migrate redistribute statements redistribute_path = ['protocols', 'bgp', asn, 'redistribute'] if config.exists(redistribute_path): config.set(bgp_path + af_path + ['redistribute']) redistributes = config.list_nodes(redistribute_path) for redistribute in redistributes: config.set(bgp_path + af_path + ['redistribute', redistribute]) if config.exists(redistribute_path + [redistribute, 'metric']): redist_metric = config.return_value(redistribute_path + [redistribute, 'metric']) config.set(bgp_path + af_path + ['redistribute', redistribute, 'metric'], value=redist_metric) if config.exists(redistribute_path + [redistribute, 'route-map']): redist_route_map = config.return_value(redistribute_path + [redistribute, 'route-map']) config.set(bgp_path + af_path + ['redistribute', redistribute, 'route-map'], value=redist_route_map) config.delete(redistribute_path) - - try: - with open(file_name, 'w') as f: - f.write(config.to_string()) - except OSError as e: - print("Failed to save the modified config: {}".format(e)) - sys.exit(1) diff --git a/src/migration-scripts/quagga/3-to-4 b/src/migration-scripts/quagga/3-to-4 old mode 100755 new mode 100644 index 1e8c8e2f2..81cf139f6 --- a/src/migration-scripts/quagga/3-to-4 +++ b/src/migration-scripts/quagga/3-to-4 @@ -1,76 +1,52 @@ -#!/usr/bin/env python3 +# Copyright 2019-2024 VyOS maintainers and contributors <maintainers@vyos.io> # -# Copyright (C) 2020 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 program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, +# 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 General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. -# +# 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 <http://www.gnu.org/licenses/>. # Between 1.2.3 and 1.2.4, FRR added per-neighbor enforce-first-as option. # Unfortunately they also removed the global enforce-first-as option, # which broke all old configs that used to have it. # # To emulate the effect of the original option, we insert it in every neighbor # if the config used to have the original global option -import sys - from vyos.configtree import ConfigTree -if len(sys.argv) < 2: - print("Must specify file name!") - sys.exit(1) - -file_name = sys.argv[1] +def migrate(config: ConfigTree) -> None: + if not config.exists(['protocols', 'bgp']): + # Nothing to do + return -with open(file_name, 'r') as f: - config_file = f.read() - -config = ConfigTree(config_file) - -if not config.exists(['protocols', 'bgp']): - # Nothing to do - sys.exit(0) -else: # Check if BGP is actually configured and obtain the ASN asn_list = config.list_nodes(['protocols', 'bgp']) if asn_list: # There's always just one BGP node, if any asn = asn_list[0] else: # There's actually no BGP, just its empty shell - sys.exit(0) + return # Check if BGP enforce-first-as option is set enforce_first_as_path = ['protocols', 'bgp', asn, 'parameters', 'enforce-first-as'] if config.exists(enforce_first_as_path): # Delete the obsolete option config.delete(enforce_first_as_path) # Now insert it in every peer peers = config.list_nodes(['protocols', 'bgp', asn, 'neighbor']) for p in peers: config.set(['protocols', 'bgp', asn, 'neighbor', p, 'enforce-first-as']) else: # Do nothing - sys.exit(0) - - # Save a new configuration file - try: - with open(file_name, 'w') as f: - f.write(config.to_string()) - except OSError as e: - print("Failed to save the modified config: {}".format(e)) - sys.exit(1) - + return diff --git a/src/migration-scripts/quagga/4-to-5 b/src/migration-scripts/quagga/4-to-5 old mode 100755 new mode 100644 index fcb496a9c..27b995431 --- a/src/migration-scripts/quagga/4-to-5 +++ b/src/migration-scripts/quagga/4-to-5 @@ -1,63 +1,40 @@ -#!/usr/bin/env python3 +# Copyright 2020-2024 VyOS maintainers and contributors <maintainers@vyos.io> # -# Copyright (C) 2019 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 program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, +# 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 General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. # -# - -import sys +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. from vyos.configtree import ConfigTree -if len(sys.argv) < 2: - print("Must specify file name!") - sys.exit(1) +def migrate(config: ConfigTree) -> None: + if not config.exists(['protocols', 'bgp']): + # Nothing to do + return -file_name = sys.argv[1] - -with open(file_name, 'r') as f: - config_file = f.read() - -config = ConfigTree(config_file) - -if not config.exists(['protocols', 'bgp']): - # Nothing to do - sys.exit(0) -else: # Check if BGP is actually configured and obtain the ASN asn_list = config.list_nodes(['protocols', 'bgp']) if asn_list: # There's always just one BGP node, if any asn = asn_list[0] else: # There's actually no BGP, just its empty shell - sys.exit(0) + return # Check if BGP scan-time parameter exist scan_time_param = ['protocols', 'bgp', asn, 'parameters', 'scan-time'] if config.exists(scan_time_param): # Delete BGP scan-time parameter config.delete(scan_time_param) else: # Do nothing - sys.exit(0) - - # Save a new configuration file - try: - with open(file_name, 'w') as f: - f.write(config.to_string()) - except OSError as e: - print("Failed to save the modified config: {}".format(e)) - sys.exit(1) + return diff --git a/src/migration-scripts/quagga/5-to-6 b/src/migration-scripts/quagga/5-to-6 old mode 100755 new mode 100644 index f075fc2e7..08fd070de --- a/src/migration-scripts/quagga/5-to-6 +++ b/src/migration-scripts/quagga/5-to-6 @@ -1,63 +1,40 @@ -#!/usr/bin/env python3 +# Copyright 2020-2024 VyOS maintainers and contributors <maintainers@vyos.io> # -# Copyright (C) 2020 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 program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, +# 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 General Public License for more details. +# 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 General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. - -# * Remove parameter 'disable-network-import-check' which, as implemented, -# had no effect on boot. - -import sys +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. from vyos.configtree import ConfigTree -if len(sys.argv) < 2: - print("Must specify file name!") - sys.exit(1) - -file_name = sys.argv[1] +def migrate(config: ConfigTree) -> None: + if not config.exists(['protocols', 'bgp']): + # Nothing to do + return -with open(file_name, 'r') as f: - config_file = f.read() - -config = ConfigTree(config_file) - -if not config.exists(['protocols', 'bgp']): - # Nothing to do - sys.exit(0) -else: # Check if BGP is actually configured and obtain the ASN asn_list = config.list_nodes(['protocols', 'bgp']) if asn_list: # There's always just one BGP node, if any asn = asn_list[0] else: # There's actually no BGP, just its empty shell - sys.exit(0) + return # Check if BGP parameter disable-network-import-check exists param = ['protocols', 'bgp', asn, 'parameters', 'disable-network-import-check'] if config.exists(param): # Delete parameter config.delete(param) else: # Do nothing - sys.exit(0) - - try: - with open(file_name, 'w') as f: - f.write(config.to_string()) - except OSError as e: - print("Failed to save the modified config: {}".format(e)) - sys.exit(1) + return diff --git a/src/migration-scripts/quagga/6-to-7 b/src/migration-scripts/quagga/6-to-7 old mode 100755 new mode 100644 index ed295a95c..095baac03 --- a/src/migration-scripts/quagga/6-to-7 +++ b/src/migration-scripts/quagga/6-to-7 @@ -1,116 +1,97 @@ -#!/usr/bin/env python3 +# Copyright 2021-2024 VyOS maintainers and contributors <maintainers@vyos.io> # -# Copyright (C) 2021 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 program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, +# 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 General Public License for more details. +# 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 General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. # - T3037, BGP address-family ipv6-unicast capability dynamic does not exist in # FRR, there is only a base, per neighbor dynamic capability, migrate config -from sys import argv -from sys import exit from vyos.configtree import ConfigTree from vyos.template import is_ipv4 from vyos.template import is_ipv6 -if len(argv) < 2: - print("Must specify file name!") - exit(1) - -file_name = argv[1] - -with open(file_name, 'r') as f: - config_file = f.read() - base = ['protocols', 'bgp'] -config = ConfigTree(config_file) - -if not config.exists(base): - # Nothing to do - exit(0) - -# Check if BGP is actually configured and obtain the ASN -asn_list = config.list_nodes(base) -if asn_list: - # There's always just one BGP node, if any - bgp_base = base + [asn_list[0]] - - for neighbor_type in ['neighbor', 'peer-group']: - if not config.exists(bgp_base + [neighbor_type]): - continue - for neighbor in config.list_nodes(bgp_base + [neighbor_type]): - # T2844 - add IPv4 AFI disable-send-community support - send_comm_path = bgp_base + [neighbor_type, neighbor, 'disable-send-community'] - if config.exists(send_comm_path): - new_base = bgp_base + [neighbor_type, neighbor, 'address-family', 'ipv4-unicast'] - config.set(new_base) - config.copy(send_comm_path, new_base + ['disable-send-community']) - config.delete(send_comm_path) - - cap_dynamic = False - peer_group = None - for afi in ['ipv4-unicast', 'ipv6-unicast']: - afi_path = bgp_base + [neighbor_type, neighbor, 'address-family', afi] - # Exit loop early if AFI does not exist - if not config.exists(afi_path): - continue - - cap_path = afi_path + ['capability', 'dynamic'] - if config.exists(cap_path): - cap_dynamic = True - config.delete(cap_path) - - # We have now successfully migrated the address-family - # specific dynamic capability to the neighbor/peer-group - # level. If this has been the only option under the - # address-family nodes, we can clean them up by checking if - # no other nodes are left under that tree and if so, delete - # the parent. - # - # We walk from the most inner node to the most outer one. - cleanup = -1 - while len(config.list_nodes(cap_path[:cleanup])) == 0: - config.delete(cap_path[:cleanup]) - cleanup -= 1 - - peer_group_path = afi_path + ['peer-group'] - if config.exists(peer_group_path): - if ((is_ipv4(neighbor) and afi == 'ipv4-unicast') or - (is_ipv6(neighbor) and afi == 'ipv6-unicast')): - peer_group = config.return_value(peer_group_path) - - config.delete(peer_group_path) - - # We have now successfully migrated the address-family - # specific peer-group to the neighbor level. If this has - # been the only option under the address-family nodes, we - # can clean them up by checking if no other nodes are left - # under that tree and if so, delete the parent. - # - # We walk from the most inner node to the most outer one. - cleanup = -1 - while len(config.list_nodes(peer_group_path[:cleanup])) == 0: - config.delete(peer_group_path[:cleanup]) - cleanup -= 1 - - if cap_dynamic: - config.set(bgp_base + [neighbor_type, neighbor, 'capability', 'dynamic']) - if peer_group: - config.set(bgp_base + [neighbor_type, neighbor, 'peer-group'], value=peer_group) -try: - with open(file_name, 'w') as f: - f.write(config.to_string()) -except OSError as e: - print("Failed to save the modified config: {}".format(e)) - exit(1) +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + # Nothing to do + return + + # Check if BGP is actually configured and obtain the ASN + asn_list = config.list_nodes(base) + if asn_list: + # There's always just one BGP node, if any + bgp_base = base + [asn_list[0]] + + for neighbor_type in ['neighbor', 'peer-group']: + if not config.exists(bgp_base + [neighbor_type]): + continue + for neighbor in config.list_nodes(bgp_base + [neighbor_type]): + # T2844 - add IPv4 AFI disable-send-community support + send_comm_path = bgp_base + [neighbor_type, neighbor, 'disable-send-community'] + if config.exists(send_comm_path): + new_base = bgp_base + [neighbor_type, neighbor, 'address-family', 'ipv4-unicast'] + config.set(new_base) + config.copy(send_comm_path, new_base + ['disable-send-community']) + config.delete(send_comm_path) + + cap_dynamic = False + peer_group = None + for afi in ['ipv4-unicast', 'ipv6-unicast']: + afi_path = bgp_base + [neighbor_type, neighbor, 'address-family', afi] + # Exit loop early if AFI does not exist + if not config.exists(afi_path): + continue + + cap_path = afi_path + ['capability', 'dynamic'] + if config.exists(cap_path): + cap_dynamic = True + config.delete(cap_path) + + # We have now successfully migrated the address-family + # specific dynamic capability to the neighbor/peer-group + # level. If this has been the only option under the + # address-family nodes, we can clean them up by checking if + # no other nodes are left under that tree and if so, delete + # the parent. + # + # We walk from the most inner node to the most outer one. + cleanup = -1 + while len(config.list_nodes(cap_path[:cleanup])) == 0: + config.delete(cap_path[:cleanup]) + cleanup -= 1 + + peer_group_path = afi_path + ['peer-group'] + if config.exists(peer_group_path): + if ((is_ipv4(neighbor) and afi == 'ipv4-unicast') or + (is_ipv6(neighbor) and afi == 'ipv6-unicast')): + peer_group = config.return_value(peer_group_path) + + config.delete(peer_group_path) + + # We have now successfully migrated the address-family + # specific peer-group to the neighbor level. If this has + # been the only option under the address-family nodes, we + # can clean them up by checking if no other nodes are left + # under that tree and if so, delete the parent. + # + # We walk from the most inner node to the most outer one. + cleanup = -1 + while len(config.list_nodes(peer_group_path[:cleanup])) == 0: + config.delete(peer_group_path[:cleanup]) + cleanup -= 1 + + if cap_dynamic: + config.set(bgp_base + [neighbor_type, neighbor, 'capability', 'dynamic']) + if peer_group: + config.set(bgp_base + [neighbor_type, neighbor, 'peer-group'], value=peer_group) diff --git a/src/migration-scripts/quagga/7-to-8 b/src/migration-scripts/quagga/7-to-8 old mode 100755 new mode 100644 index 8f11bf390..d9de26d15 --- a/src/migration-scripts/quagga/7-to-8 +++ b/src/migration-scripts/quagga/7-to-8 @@ -1,61 +1,42 @@ -#!/usr/bin/env python3 +# Copyright 2021-2024 VyOS maintainers and contributors <maintainers@vyos.io> # -# Copyright (C) 2021 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 program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, +# 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 General Public License for more details. +# 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 General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. # - T3391: Migrate "maximum-paths" setting from "protocols bgp asn maximum-paths" # under the IPv4 address-family tree. Reason is we currently have no way in # configuring this for IPv6 address-family. This mimics the FRR configuration. -from sys import argv -from sys import exit from vyos.configtree import ConfigTree -if len(argv) < 2: - print("Must specify file name!") - exit(1) - -file_name = argv[1] - -with open(file_name, 'r') as f: - config_file = f.read() - base = ['protocols', 'bgp'] -config = ConfigTree(config_file) - -if not config.exists(base): - # Nothing to do - exit(0) - -# Check if BGP is actually configured and obtain the ASN -asn_list = config.list_nodes(base) -if asn_list: - # There's always just one BGP node, if any - bgp_base = base + [asn_list[0]] - - maximum_paths = bgp_base + ['maximum-paths'] - if config.exists(maximum_paths): - for bgp_type in ['ebgp', 'ibgp']: - if config.exists(maximum_paths + [bgp_type]): - new_base = bgp_base + ['address-family', 'ipv4-unicast', 'maximum-paths'] - config.set(new_base) - config.copy(maximum_paths + [bgp_type], new_base + [bgp_type]) - config.delete(maximum_paths) -try: - with open(file_name, 'w') as f: - f.write(config.to_string()) -except OSError as e: - print("Failed to save the modified config: {}".format(e)) - exit(1) +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + # Nothing to do + return + + # Check if BGP is actually configured and obtain the ASN + asn_list = config.list_nodes(base) + if asn_list: + # There's always just one BGP node, if any + bgp_base = base + [asn_list[0]] + + maximum_paths = bgp_base + ['maximum-paths'] + if config.exists(maximum_paths): + for bgp_type in ['ebgp', 'ibgp']: + if config.exists(maximum_paths + [bgp_type]): + new_base = bgp_base + ['address-family', 'ipv4-unicast', 'maximum-paths'] + config.set(new_base) + config.copy(maximum_paths + [bgp_type], new_base + [bgp_type]) + config.delete(maximum_paths) diff --git a/src/migration-scripts/quagga/8-to-9 b/src/migration-scripts/quagga/8-to-9 old mode 100755 new mode 100644 index 0f683d5a1..eece6c15d --- a/src/migration-scripts/quagga/8-to-9 +++ b/src/migration-scripts/quagga/8-to-9 @@ -1,137 +1,117 @@ -#!/usr/bin/env python3 +# Copyright 2021-2024 VyOS maintainers and contributors <maintainers@vyos.io> # -# Copyright (C) 2021 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 program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, +# 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 General Public License for more details. +# 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 General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. # - T2450: drop interface-route and interface-route6 from "protocols static" -from sys import argv -from sys import exit - from vyos.configtree import ConfigTree def migrate_interface_route(config, base, path, route_route6): """ Generic migration function which can be called on every instance of interface-route, beeing it ipv4, ipv6 or nested under the "static table" nodes. What we do? - Drop 'interface-route' or 'interface-route6' and migrate the route unter the 'route' or 'route6' tag node. """ if config.exists(base + path): for route in config.list_nodes(base + path): interface = config.list_nodes(base + path + [route, 'next-hop-interface']) tmp = base + path + [route, 'next-hop-interface'] for interface in config.list_nodes(tmp): new_base = base + [route_route6, route, 'interface'] config.set(new_base) config.set_tag(base + [route_route6]) config.set_tag(new_base) config.copy(tmp + [interface], new_base + [interface]) config.delete(base + path) def migrate_route(config, base, path, route_route6): """ Generic migration function which can be called on every instance of route, beeing it ipv4, ipv6 or even nested under the static table nodes. What we do? - for consistency reasons rename next-hop-interface to interface - for consistency reasons rename next-hop-vrf to vrf """ if config.exists(base + path): for route in config.list_nodes(base + path): next_hop = base + path + [route, 'next-hop'] if config.exists(next_hop): for gateway in config.list_nodes(next_hop): # IPv4 routes calls it next-hop-interface, rename this to # interface instead so it's consitent with IPv6 interface_path = next_hop + [gateway, 'next-hop-interface'] if config.exists(interface_path): config.rename(interface_path, 'interface') # When VRFs got introduced, I (c-po) named it next-hop-vrf, # we can also call it vrf which is simply shorter. vrf_path = next_hop + [gateway, 'next-hop-vrf'] if config.exists(vrf_path): config.rename(vrf_path, 'vrf') next_hop = base + path + [route, 'interface'] if config.exists(next_hop): for interface in config.list_nodes(next_hop): # IPv4 routes calls it next-hop-interface, rename this to # interface instead so it's consitent with IPv6 interface_path = next_hop + [interface, 'next-hop-interface'] if config.exists(interface_path): config.rename(interface_path, 'interface') # When VRFs got introduced, I (c-po) named it next-hop-vrf, # we can also call it vrf which is simply shorter. vrf_path = next_hop + [interface, 'next-hop-vrf'] if config.exists(vrf_path): config.rename(vrf_path, 'vrf') -if len(argv) < 2: - print("Must specify file name!") - exit(1) +base = ['protocols', 'static'] -file_name = argv[1] +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + # Nothing to do + return -with open(file_name, 'r') as f: - config_file = f.read() + # Migrate interface-route into route + migrate_interface_route(config, base, ['interface-route'], 'route') -base = ['protocols', 'static'] + # Migrate interface-route6 into route6 + migrate_interface_route(config, base, ['interface-route6'], 'route6') -config = ConfigTree(config_file) -if not config.exists(base): - # Nothing to do - exit(0) + # Cleanup nodes inside route + migrate_route(config, base, ['route'], 'route') -# Migrate interface-route into route -migrate_interface_route(config, base, ['interface-route'], 'route') + # Cleanup nodes inside route6 + migrate_route(config, base, ['route6'], 'route6') -# Migrate interface-route6 into route6 -migrate_interface_route(config, base, ['interface-route6'], 'route6') + # + # PBR table cleanup + table_path = base + ['table'] + if config.exists(table_path): + for table in config.list_nodes(table_path): + # Migrate interface-route into route + migrate_interface_route(config, table_path + [table], ['interface-route'], 'route') -# Cleanup nodes inside route -migrate_route(config, base, ['route'], 'route') + # Migrate interface-route6 into route6 + migrate_interface_route(config, table_path + [table], ['interface-route6'], 'route6') -# Cleanup nodes inside route6 -migrate_route(config, base, ['route6'], 'route6') + # Cleanup nodes inside route + migrate_route(config, table_path + [table], ['route'], 'route') -# -# PBR table cleanup -table_path = base + ['table'] -if config.exists(table_path): - for table in config.list_nodes(table_path): - # Migrate interface-route into route - migrate_interface_route(config, table_path + [table], ['interface-route'], 'route') - - # Migrate interface-route6 into route6 - migrate_interface_route(config, table_path + [table], ['interface-route6'], 'route6') - - # Cleanup nodes inside route - migrate_route(config, table_path + [table], ['route'], 'route') - - # Cleanup nodes inside route6 - migrate_route(config, table_path + [table], ['route6'], 'route6') - -try: - with open(file_name, 'w') as f: - f.write(config.to_string()) -except OSError as e: - print("Failed to save the modified config: {}".format(e)) - exit(1) + # Cleanup nodes inside route6 + migrate_route(config, table_path + [table], ['route6'], 'route6') diff --git a/src/migration-scripts/quagga/9-to-10 b/src/migration-scripts/quagga/9-to-10 old mode 100755 new mode 100644 index 3731762f7..4ac1f0b7d --- a/src/migration-scripts/quagga/9-to-10 +++ b/src/migration-scripts/quagga/9-to-10 @@ -1,62 +1,42 @@ -#!/usr/bin/env python3 +# Copyright 2022-2024 VyOS maintainers and contributors <maintainers@vyos.io> # -# Copyright (C) 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 program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, +# 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 General Public License for more details. +# 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 General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. # re-organize route-map as-path -from sys import argv -from sys import exit - from vyos.configtree import ConfigTree -if len(argv) < 2: - print("Must specify file name!") - exit(1) - -file_name = argv[1] - -with open(file_name, 'r') as f: - config_file = f.read() - base = ['policy', 'route-map'] -config = ConfigTree(config_file) -if not config.exists(base): - # Nothing to do - exit(0) - -for route_map in config.list_nodes(base): - # Bail out Early - if not config.exists(base + [route_map, 'rule']): - continue - - for rule in config.list_nodes(base + [route_map, 'rule']): - rule_base = base + [route_map, 'rule', rule] - if config.exists(rule_base + ['set', 'as-path-exclude']): - tmp = config.return_value(rule_base + ['set', 'as-path-exclude']) - config.delete(rule_base + ['set', 'as-path-exclude']) - config.set(rule_base + ['set', 'as-path', 'exclude'], value=tmp) - - if config.exists(rule_base + ['set', 'as-path-prepend']): - tmp = config.return_value(rule_base + ['set', 'as-path-prepend']) - config.delete(rule_base + ['set', 'as-path-prepend']) - config.set(rule_base + ['set', 'as-path', 'prepend'], value=tmp) - -try: - with open(file_name, 'w') as f: - f.write(config.to_string()) -except OSError as e: - print("Failed to save the modified config: {}".format(e)) - exit(1) +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + # Nothing to do + return + + for route_map in config.list_nodes(base): + # Bail out Early + if not config.exists(base + [route_map, 'rule']): + continue + + for rule in config.list_nodes(base + [route_map, 'rule']): + rule_base = base + [route_map, 'rule', rule] + if config.exists(rule_base + ['set', 'as-path-exclude']): + tmp = config.return_value(rule_base + ['set', 'as-path-exclude']) + config.delete(rule_base + ['set', 'as-path-exclude']) + config.set(rule_base + ['set', 'as-path', 'exclude'], value=tmp) + + if config.exists(rule_base + ['set', 'as-path-prepend']): + tmp = config.return_value(rule_base + ['set', 'as-path-prepend']) + config.delete(rule_base + ['set', 'as-path-prepend']) + config.set(rule_base + ['set', 'as-path', 'prepend'], value=tmp) diff --git a/src/migration-scripts/reverse-proxy/0-to-1 b/src/migration-scripts/reverse-proxy/0-to-1 old mode 100755 new mode 100644 index d61493815..b495474a6 --- a/src/migration-scripts/reverse-proxy/0-to-1 +++ b/src/migration-scripts/reverse-proxy/0-to-1 @@ -1,48 +1,31 @@ -#!/usr/bin/env python3 +# Copyright 2024 VyOS maintainers and contributors <maintainers@vyos.io> # -# Copyright (C) 2024 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 program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, +# 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 General Public License for more details. +# 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 General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. # T6409: Remove unused 'backend bk-example parameters' node -from sys import argv, exit from vyos.configtree import ConfigTree -if len(argv) < 2: - print("Must specify file name!") - exit(1) - -file_name = argv[1] - -with open(file_name, 'r') as f: - config_file = f.read() - -config = ConfigTree(config_file) base = ['load-balancing', 'reverse-proxy', 'backend'] -if not config.exists(base): - # Nothing to do - exit(0) -# we need to run this for every configured network -for backend in config.list_nodes(base): - param_node = base + [backend, 'parameters'] - if config.exists(param_node): - config.delete(param_node) +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + # Nothing to do + return -try: - with open(file_name, 'w') as f: - f.write(config.to_string()) -except OSError as e: - print("Failed to save the modified config: {}".format(e)) - exit(1) + # we need to run this for every configured network + for backend in config.list_nodes(base): + param_node = base + [backend, 'parameters'] + if config.exists(param_node): + config.delete(param_node) diff --git a/src/migration-scripts/rip/0-to-1 b/src/migration-scripts/rip/0-to-1 old mode 100755 new mode 100644 index 08a866374..6d41bcf58 --- a/src/migration-scripts/rip/0-to-1 +++ b/src/migration-scripts/rip/0-to-1 @@ -1,51 +1,31 @@ -#!/usr/bin/env python3 +# Copyright 2023-2024 VyOS maintainers and contributors <maintainers@vyos.io> # -# Copyright (C) 2023 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 program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, +# 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 General Public License for more details. +# 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 General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. # T5150: Rework CLI definitions to apply route-maps between routing daemons # and zebra/kernel -from sys import argv -from sys import exit - from vyos.configtree import ConfigTree -if len(argv) < 2: - print("Must specify file name!") - exit(1) - -file_name = argv[1] - -with open(file_name, 'r') as f: - config_file = f.read() - -config = ConfigTree(config_file) - ripng_base = ['protocols', 'ripng'] -# Check if RIPng is configured - if so, migrate the CLI node -if config.exists(ripng_base): - if config.exists(ripng_base + ['route-map']): - tmp = config.return_value(ripng_base + ['route-map']) - config.set(['system', 'ipv6', 'protocol', 'ripng', 'route-map'], value=tmp) - config.set_tag(['system', 'ipv6', 'protocol']) - config.delete(ripng_base + ['route-map']) +def migrate(config: ConfigTree) -> None: + # Check if RIPng is configured - if so, migrate the CLI node + if config.exists(ripng_base): + if config.exists(ripng_base + ['route-map']): + tmp = config.return_value(ripng_base + ['route-map']) -try: - with open(file_name, 'w') as f: - f.write(config.to_string()) -except OSError as e: - print(f'Failed to save the modified config: {e}') - exit(1) + config.set(['system', 'ipv6', 'protocol', 'ripng', 'route-map'], value=tmp) + config.set_tag(['system', 'ipv6', 'protocol']) + config.delete(ripng_base + ['route-map']) diff --git a/src/migration-scripts/rpki/0-to-1 b/src/migration-scripts/rpki/0-to-1 old mode 100755 new mode 100644 index a7b5d07d5..b6e781fa9 --- a/src/migration-scripts/rpki/0-to-1 +++ b/src/migration-scripts/rpki/0-to-1 @@ -1,63 +1,44 @@ -#!/usr/bin/env python3 +# Copyright 2021-2024 VyOS maintainers and contributors <maintainers@vyos.io> # -# Copyright (C) 2021 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 program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, +# 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 General Public License for more details. +# 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 General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. -from sys import exit -from sys import argv from vyos.configtree import ConfigTree -if len(argv) < 2: - print("Must specify file name!") - exit(1) - -file_name = argv[1] - -with open(file_name, 'r') as f: - config_file = f.read() - base = ['protocols', 'rpki'] -config = ConfigTree(config_file) - -# Nothing to do -if not config.exists(base): - exit(0) - -if config.exists(base + ['cache']): - preference = 1 - for cache in config.list_nodes(base + ['cache']): - address_node = base + ['cache', cache, 'address'] - if config.exists(address_node): - address = config.return_value(address_node) - # We do not longer support the address leafNode, RPKI cache server - # IP address is now used from the tagNode - config.delete(address_node) - # VyOS 1.2 had no per instance preference, setting new defaults - config.set(base + ['cache', cache, 'preference'], value=preference) - # Increase preference for the next caching peer - actually VyOS 1.2 - # supported only one but better save then sorry (T3253) - preference += 1 - - # T3293: If the RPKI cache name equals the configured address, - # renaming is not possible, as rename expects the new path to not - # exist. - if not config.exists(base + ['cache', address]): - config.rename(base + ['cache', cache], address) -try: - with open(file_name, 'w') as f: - f.write(config.to_string()) -except OSError as e: - print("Failed to save the modified config: {}".format(e)) - exit(1) +def migrate(config: ConfigTree) -> None: + # Nothing to do + if not config.exists(base): + return + + if config.exists(base + ['cache']): + preference = 1 + for cache in config.list_nodes(base + ['cache']): + address_node = base + ['cache', cache, 'address'] + if config.exists(address_node): + address = config.return_value(address_node) + # We do not longer support the address leafNode, RPKI cache server + # IP address is now used from the tagNode + config.delete(address_node) + # VyOS 1.2 had no per instance preference, setting new defaults + config.set(base + ['cache', cache, 'preference'], value=preference) + # Increase preference for the next caching peer - actually VyOS 1.2 + # supported only one but better save then sorry (T3253) + preference += 1 + + # T3293: If the RPKI cache name equals the configured address, + # renaming is not possible, as rename expects the new path to not + # exist. + if not config.exists(base + ['cache', address]): + config.rename(base + ['cache', cache], address) diff --git a/src/migration-scripts/rpki/1-to-2 b/src/migration-scripts/rpki/1-to-2 old mode 100755 new mode 100644 index 50d4a3dfc..855236d6c --- a/src/migration-scripts/rpki/1-to-2 +++ b/src/migration-scripts/rpki/1-to-2 @@ -1,73 +1,53 @@ -#!/usr/bin/env python3 +# Copyright 2024 VyOS maintainers and contributors <maintainers@vyos.io> # -# Copyright (C) 2024 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 program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, +# 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 General Public License for more details. +# 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 General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. # T6011: rpki: known-hosts-file is no longer supported bxy FRR CLI, # remove VyOS CLI node -from sys import exit -from sys import argv - from vyos.configtree import ConfigTree from vyos.pki import OPENSSH_KEY_BEGIN from vyos.pki import OPENSSH_KEY_END from vyos.utils.file import read_file -if len(argv) < 2: - print("Must specify file name!") - exit(1) - -file_name = argv[1] - -with open(file_name, 'r') as f: - config_file = f.read() - base = ['protocols', 'rpki'] -config = ConfigTree(config_file) - -# Nothing to do -if not config.exists(base): - exit(0) - -if config.exists(base + ['cache']): - for cache in config.list_nodes(base + ['cache']): - ssh_node = base + ['cache', cache, 'ssh'] - if config.exists(ssh_node + ['known-hosts-file']): - config.delete(ssh_node + ['known-hosts-file']) - - if config.exists(base + ['cache', cache, 'ssh']): - private_key_node = base + ['cache', cache, 'ssh', 'private-key-file'] - private_key_file = config.return_value(private_key_node) - private_key = read_file(private_key_file).replace(OPENSSH_KEY_BEGIN, '').replace(OPENSSH_KEY_END, '').replace('\n','') - - public_key_node = base + ['cache', cache, 'ssh', 'public-key-file'] - public_key_file = config.return_value(public_key_node) - public_key = read_file(public_key_file).split() - - config.set(['pki', 'openssh', f'rpki-{cache}', 'private', 'key'], value=private_key) - config.set(['pki', 'openssh', f'rpki-{cache}', 'public', 'key'], value=public_key[1]) - config.set(['pki', 'openssh', f'rpki-{cache}', 'public', 'type'], value=public_key[0]) - config.set_tag(['pki', 'openssh']) - config.set(ssh_node + ['key'], value=f'rpki-{cache}') - - config.delete(private_key_node) - config.delete(public_key_node) -try: - with open(file_name, 'w') as f: - f.write(config.to_string()) -except OSError as e: - print("Failed to save the modified config: {}".format(e)) - exit(1) +def migrate(config: ConfigTree) -> None: + # Nothing to do + if not config.exists(base): + return + + if config.exists(base + ['cache']): + for cache in config.list_nodes(base + ['cache']): + ssh_node = base + ['cache', cache, 'ssh'] + if config.exists(ssh_node + ['known-hosts-file']): + config.delete(ssh_node + ['known-hosts-file']) + + if config.exists(base + ['cache', cache, 'ssh']): + private_key_node = base + ['cache', cache, 'ssh', 'private-key-file'] + private_key_file = config.return_value(private_key_node) + private_key = read_file(private_key_file).replace(OPENSSH_KEY_BEGIN, '').replace(OPENSSH_KEY_END, '').replace('\n','') + + public_key_node = base + ['cache', cache, 'ssh', 'public-key-file'] + public_key_file = config.return_value(public_key_node) + public_key = read_file(public_key_file).split() + + config.set(['pki', 'openssh', f'rpki-{cache}', 'private', 'key'], value=private_key) + config.set(['pki', 'openssh', f'rpki-{cache}', 'public', 'key'], value=public_key[1]) + config.set(['pki', 'openssh', f'rpki-{cache}', 'public', 'type'], value=public_key[0]) + config.set_tag(['pki', 'openssh']) + config.set(ssh_node + ['key'], value=f'rpki-{cache}') + + config.delete(private_key_node) + config.delete(public_key_node) diff --git a/src/migration-scripts/salt/0-to-1 b/src/migration-scripts/salt/0-to-1 old mode 100755 new mode 100644 index 481d9de8f..3990a88dc --- a/src/migration-scripts/salt/0-to-1 +++ b/src/migration-scripts/salt/0-to-1 @@ -1,58 +1,38 @@ -#!/usr/bin/env python3 +# Copyright 2020-2024 VyOS maintainers and contributors <maintainers@vyos.io> # -# Copyright (C) 2020 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 program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, +# 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 General Public License for more details. +# 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 General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. # Delete log_file, log_level and user nodes # rename hash_type to hash # rename mine_interval to interval -from sys import argv,exit - from vyos.configtree import ConfigTree -if len(argv) < 2: - print("Must specify file name!") - exit(1) - -file_name = argv[1] - -with open(file_name, 'r') as f: - config_file = f.read() - -config = ConfigTree(config_file) - base = ['service', 'salt-minion'] -if not config.exists(base): - # Nothing to do - exit(0) -else: + +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + # Nothing to do + return # delete nodes which are now populated with sane defaults for node in ['log_file', 'log_level', 'user']: if config.exists(base + [node]): config.delete(base + [node]) if config.exists(base + ['hash_type']): config.rename(base + ['hash_type'], 'hash') if config.exists(base + ['mine_interval']): config.rename(base + ['mine_interval'], 'interval') - - try: - with open(file_name, 'w') as f: - f.write(config.to_string()) - except OSError as e: - print("Failed to save the modified config: {}".format(e)) - exit(1) diff --git a/src/migration-scripts/snmp/0-to-1 b/src/migration-scripts/snmp/0-to-1 old mode 100755 new mode 100644 index b1e61b958..03b190cb7 --- a/src/migration-scripts/snmp/0-to-1 +++ b/src/migration-scripts/snmp/0-to-1 @@ -1,56 +1,38 @@ -#!/usr/bin/env python3 +# Copyright 2019-2024 VyOS maintainers and contributors <maintainers@vyos.io> # -# Copyright (C) 2019 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 program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, +# 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 General Public License for more details. +# 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 General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. -import sys from vyos.configtree import ConfigTree -if len(sys.argv) < 2: - print("Must specify file name!") - sys.exit(1) - -file_name = sys.argv[1] - -with open(file_name, 'r') as f: - config_file = f.read() - -config = ConfigTree(config_file) config_base = ['service', 'snmp', 'v3'] -if not config.exists(config_base): - # Nothing to do - sys.exit(0) -else: +def migrate(config: ConfigTree) -> None: + if not config.exists(config_base): + # Nothing to do + return + # we no longer support a per trap target engine ID (https://vyos.dev/T818) if config.exists(config_base + ['v3', 'trap-target']): for target in config.list_nodes(config_base + ['v3', 'trap-target']): config.delete(config_base + ['v3', 'trap-target', target, 'engineid']) # we no longer support a per user engine ID (https://vyos.dev/T818) if config.exists(config_base + ['v3', 'user']): for user in config.list_nodes(config_base + ['v3', 'user']): config.delete(config_base + ['v3', 'user', user, 'engineid']) # we drop TSM support as there seem to be no users and this code is untested # https://vyos.dev/T1769 if config.exists(config_base + ['v3', 'tsm']): config.delete(config_base + ['v3', 'tsm']) - - try: - with open(file_name, 'w') as f: - f.write(config.to_string()) - except OSError as e: - print("Failed to save the modified config: {}".format(e)) - sys.exit(1) diff --git a/src/migration-scripts/snmp/1-to-2 b/src/migration-scripts/snmp/1-to-2 old mode 100755 new mode 100644 index e02cd1aa1..0120f8acb --- a/src/migration-scripts/snmp/1-to-2 +++ b/src/migration-scripts/snmp/1-to-2 @@ -1,89 +1,70 @@ -#!/usr/bin/env python3 +# Copyright 2020-2024 VyOS maintainers and contributors <maintainers@vyos.io> # -# Copyright (C) 2020 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 program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, +# 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 General Public License for more details. +# 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 General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. -from sys import argv, exit from vyos.configtree import ConfigTree +# We no longer support hashed values prefixed with '0x' to unclutter +# CLI and also calculate the hases in advance instead of retrieving +# them after service startup - which was always a bad idea +prefix = '0x' + def migrate_keys(config, path): # authentication: rename node 'encrypted-key' -> 'encrypted-password' config_path_auth = path + ['auth', 'encrypted-key'] if config.exists(config_path_auth): config.rename(config_path_auth, 'encrypted-password') config_path_auth = path + ['auth', 'encrypted-password'] # remove leading '0x' from string if present tmp = config.return_value(config_path_auth) if tmp.startswith(prefix): tmp = tmp.replace(prefix, '') config.set(config_path_auth, value=tmp) # privacy: rename node 'encrypted-key' -> 'encrypted-password' config_path_priv = path + ['privacy', 'encrypted-key'] if config.exists(config_path_priv): config.rename(config_path_priv, 'encrypted-password') config_path_priv = path + ['privacy', 'encrypted-password'] # remove leading '0x' from string if present tmp = config.return_value(config_path_priv) if tmp.startswith(prefix): tmp = tmp.replace(prefix, '') config.set(config_path_priv, value=tmp) -if __name__ == '__main__': - if len(argv) < 2: - print("Must specify file name!") - exit(1) - - file_name = argv[1] - - with open(file_name, 'r') as f: - config_file = f.read() - - config = ConfigTree(config_file) +def migrate(config: ConfigTree) -> None: config_base = ['service', 'snmp', 'v3'] if not config.exists(config_base): # Nothing to do - exit(0) - else: - # We no longer support hashed values prefixed with '0x' to unclutter - # CLI and also calculate the hases in advance instead of retrieving - # them after service startup - which was always a bad idea - prefix = '0x' - - config_engineid = config_base + ['engineid'] - if config.exists(config_engineid): - tmp = config.return_value(config_engineid) - if tmp.startswith(prefix): - tmp = tmp.replace(prefix, '') - config.set(config_engineid, value=tmp) + return - config_user = config_base + ['user'] - if config.exists(config_user): - for user in config.list_nodes(config_user): - migrate_keys(config, config_user + [user]) + config_engineid = config_base + ['engineid'] + if config.exists(config_engineid): + tmp = config.return_value(config_engineid) + if tmp.startswith(prefix): + tmp = tmp.replace(prefix, '') + config.set(config_engineid, value=tmp) - config_trap = config_base + ['trap-target'] - if config.exists(config_trap): - for trap in config.list_nodes(config_trap): - migrate_keys(config, config_trap + [trap]) + config_user = config_base + ['user'] + if config.exists(config_user): + for user in config.list_nodes(config_user): + migrate_keys(config, config_user + [user]) - try: - with open(file_name, 'w') as f: - f.write(config.to_string()) - except OSError as e: - print("Failed to save the modified config: {}".format(e)) - exit(1) + config_trap = config_base + ['trap-target'] + if config.exists(config_trap): + for trap in config.list_nodes(config_trap): + migrate_keys(config, config_trap + [trap]) diff --git a/src/migration-scripts/snmp/2-to-3 b/src/migration-scripts/snmp/2-to-3 old mode 100755 new mode 100644 index ab9b5dcba..6d828b619 --- a/src/migration-scripts/snmp/2-to-3 +++ b/src/migration-scripts/snmp/2-to-3 @@ -1,54 +1,33 @@ -#!/usr/bin/env python3 +# Copyright 2024 VyOS maintainers and contributors <maintainers@vyos.io> # -# Copyright (C) 2022-2024 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 program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, +# 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 General Public License for more details. +# 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 General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. # T4857: Implement FRR SNMP recomendations # cli changes from: # set service snmp oid-enable route-table # To # set service snmp oid-enable ip-forward -from sys import argv -from sys import exit - from vyos.configtree import ConfigTree -if len(argv) < 2: - print("Must specify file name!") - exit(1) - -file_name = argv[1] - -with open(file_name, 'r') as f: - config_file = f.read() - base = ['service snmp'] -config = ConfigTree(config_file) - -if not config.exists(base): - # Nothing to do - exit(0) - -if config.exists(base + ['oid-enable']): - config.delete(base + ['oid-enable']) - config.set(base + ['oid-enable'], 'ip-forward') +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + # Nothing to do + return -try: - with open(file_name, 'w') as f: - f.write(config.to_string()) -except OSError as e: - print("Failed to save the modified config: {}".format(e)) - exit(1) + if config.exists(base + ['oid-enable']): + config.delete(base + ['oid-enable']) + config.set(base + ['oid-enable'], 'ip-forward') diff --git a/src/migration-scripts/ssh/0-to-1 b/src/migration-scripts/ssh/0-to-1 old mode 100755 new mode 100644 index 2595599ac..65b68f509 --- a/src/migration-scripts/ssh/0-to-1 +++ b/src/migration-scripts/ssh/0-to-1 @@ -1,32 +1,26 @@ -#!/usr/bin/env python3 +# Copyright 2020-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# 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 <http://www.gnu.org/licenses/>. # Delete "service ssh allow-root" option -import sys - from vyos.configtree import ConfigTree -if len(sys.argv) < 2: - print("Must specify file name!") - sys.exit(1) - -file_name = sys.argv[1] - -with open(file_name, 'r') as f: - config_file = f.read() +def migrate(config: ConfigTree) -> None: + if not config.exists(['service', 'ssh', 'allow-root']): + # Nothing to do + return -config = ConfigTree(config_file) - -if not config.exists(['service', 'ssh', 'allow-root']): - # Nothing to do - sys.exit(0) -else: # Delete node with abandoned command config.delete(['service', 'ssh', 'allow-root']) - - try: - with open(file_name, 'w') as f: - f.write(config.to_string()) - except OSError as e: - print("Failed to save the modified config: {}".format(e)) - sys.exit(1) diff --git a/src/migration-scripts/ssh/1-to-2 b/src/migration-scripts/ssh/1-to-2 old mode 100755 new mode 100644 index 79d65d7d4..b601db3b4 --- a/src/migration-scripts/ssh/1-to-2 +++ b/src/migration-scripts/ssh/1-to-2 @@ -1,81 +1,63 @@ -#!/usr/bin/env python3 +# Copyright 2020-2024 VyOS maintainers and contributors <maintainers@vyos.io> # -# Copyright (C) 2020-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 program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, +# 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 General Public License for more details. +# 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 General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. # VyOS 1.2 crux allowed configuring a lower or upper case loglevel. This # is no longer supported as the input data is validated and will lead to # an error. If user specifies an upper case logleve, make it lowercase -from sys import argv,exit from vyos.configtree import ConfigTree -if len(argv) < 2: - print("Must specify file name!") - exit(1) - -file_name = argv[1] - -with open(file_name, 'r') as f: - config_file = f.read() - base = ['service', 'ssh'] -config = ConfigTree(config_file) - -if not config.exists(base): - # Nothing to do - exit(0) - -path_loglevel = base + ['loglevel'] -if config.exists(path_loglevel): - # red in configured loglevel and convert it to lower case - tmp = config.return_value(path_loglevel).lower() - # VyOS 1.2 had no proper value validation on the CLI thus the - # user could use any arbitrary values - sanitize them - if tmp not in ['quiet', 'fatal', 'error', 'info', 'verbose']: - tmp = 'info' - config.set(path_loglevel, value=tmp) - -# T4273: migrate ssh cipher list to multi node -path_ciphers = base + ['ciphers'] -if config.exists(path_ciphers): - tmp = [] - # get curtrent cipher list - comma delimited - for cipher in config.return_values(path_ciphers): - tmp.extend(cipher.split(',')) - # delete old cipher suite representation - config.delete(path_ciphers) - - for cipher in tmp: - config.set(path_ciphers, value=cipher, replace=False) - -# T4273: migrate ssh key-exchange list to multi node -path_kex = base + ['key-exchange'] -if config.exists(path_kex): - tmp = [] - # get curtrent cipher list - comma delimited - for kex in config.return_values(path_kex): - tmp.extend(kex.split(',')) - # delete old cipher suite representation - config.delete(path_kex) - - for kex in tmp: - config.set(path_kex, value=kex, replace=False) -try: - with open(file_name, 'w') as f: - f.write(config.to_string()) -except OSError as e: - print("Failed to save the modified config: {}".format(e)) - exit(1) +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + # Nothing to do + return + + path_loglevel = base + ['loglevel'] + if config.exists(path_loglevel): + # red in configured loglevel and convert it to lower case + tmp = config.return_value(path_loglevel).lower() + # VyOS 1.2 had no proper value validation on the CLI thus the + # user could use any arbitrary values - sanitize them + if tmp not in ['quiet', 'fatal', 'error', 'info', 'verbose']: + tmp = 'info' + config.set(path_loglevel, value=tmp) + + # T4273: migrate ssh cipher list to multi node + path_ciphers = base + ['ciphers'] + if config.exists(path_ciphers): + tmp = [] + # get curtrent cipher list - comma delimited + for cipher in config.return_values(path_ciphers): + tmp.extend(cipher.split(',')) + # delete old cipher suite representation + config.delete(path_ciphers) + + for cipher in tmp: + config.set(path_ciphers, value=cipher, replace=False) + + # T4273: migrate ssh key-exchange list to multi node + path_kex = base + ['key-exchange'] + if config.exists(path_kex): + tmp = [] + # get curtrent cipher list - comma delimited + for kex in config.return_values(path_kex): + tmp.extend(kex.split(',')) + # delete old cipher suite representation + config.delete(path_kex) + + for kex in tmp: + config.set(path_kex, value=kex, replace=False) diff --git a/src/migration-scripts/sstp/0-to-1 b/src/migration-scripts/sstp/0-to-1 old mode 100755 new mode 100644 index 150127aaf..1bd7d6c6b --- a/src/migration-scripts/sstp/0-to-1 +++ b/src/migration-scripts/sstp/0-to-1 @@ -1,128 +1,109 @@ -#!/usr/bin/env python3 +# Copyright 2020-2024 VyOS maintainers and contributors <maintainers@vyos.io> # -# Copyright (C) 2020-2024 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 program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, +# 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 General Public License for more details. +# 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 General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. - +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. # - migrate from "service sstp-server" to "vpn sstp" # - remove primary/secondary identifier from nameserver # - migrate RADIUS configuration to a more uniform syntax accross the system # - authentication radius-server x.x.x.x to authentication radius server x.x.x.x # - authentication radius-settings to authentication radius # - do not migrate radius server req-limit, use default of unlimited # - migrate SSL certificate path -import sys - from vyos.configtree import ConfigTree -if len(sys.argv) < 2: - print("Must specify file name!") - sys.exit(1) - -file_name = sys.argv[1] +old_base = ['service', 'sstp-server'] -with open(file_name, 'r') as f: - config_file = f.read() +def migrate(config: ConfigTree) -> None: + if not config.exists(old_base): + # Nothing to do + return -config = ConfigTree(config_file) -old_base = ['service', 'sstp-server'] -if not config.exists(old_base): - # Nothing to do - sys.exit(0) -else: # ensure new base path exists if not config.exists(['vpn']): config.set(['vpn']) new_base = ['vpn', 'sstp'] # copy entire tree config.copy(old_base, new_base) config.delete(old_base) # migrate DNS servers dns_base = new_base + ['network-settings', 'dns-server'] if config.exists(dns_base): if config.exists(dns_base + ['primary-dns']): dns = config.return_value(dns_base + ['primary-dns']) config.set(new_base + ['network-settings', 'name-server'], value=dns, replace=False) if config.exists(dns_base + ['secondary-dns']): dns = config.return_value(dns_base + ['secondary-dns']) config.set(new_base + ['network-settings', 'name-server'], value=dns, replace=False) config.delete(dns_base) # migrate radius options - copy subtree # thus must happen before migration of the individual RADIUS servers old_options = new_base + ['authentication', 'radius-settings'] if config.exists(old_options): new_options = new_base + ['authentication', 'radius'] config.copy(old_options, new_options) config.delete(old_options) # migrate radius dynamic author / change of authorisation server dae_old = new_base + ['authentication', 'radius', 'dae-server'] if config.exists(dae_old): config.rename(dae_old, 'dynamic-author') dae_new = new_base + ['authentication', 'radius', 'dynamic-author'] if config.exists(dae_new + ['ip-address']): config.rename(dae_new + ['ip-address'], 'server') if config.exists(dae_new + ['secret']): config.rename(dae_new + ['secret'], 'key') # migrate radius server radius_server = new_base + ['authentication', 'radius-server'] if config.exists(radius_server): for server in config.list_nodes(radius_server): base = radius_server + [server] new = new_base + ['authentication', 'radius', 'server', server] # convert secret to key if config.exists(base + ['secret']): tmp = config.return_value(base + ['secret']) config.set(new + ['key'], value=tmp) if config.exists(base + ['fail-time']): tmp = config.return_value(base + ['fail-time']) config.set(new + ['fail-time'], value=tmp) config.set_tag(new_base + ['authentication', 'radius', 'server']) config.delete(radius_server) # migrate SSL certificates old_ssl = new_base + ['sstp-settings'] new_ssl = new_base + ['ssl'] config.copy(old_ssl + ['ssl-certs'], new_ssl) config.delete(old_ssl) if config.exists(new_ssl + ['ca']): config.rename(new_ssl + ['ca'], 'ca-cert-file') if config.exists(new_ssl + ['server-cert']): config.rename(new_ssl + ['server-cert'], 'cert-file') if config.exists(new_ssl + ['server-key']): config.rename(new_ssl + ['server-key'], 'key-file') - - try: - with open(file_name, 'w') as f: - f.write(config.to_string()) - except OSError as e: - print("Failed to save the modified config: {}".format(e)) - sys.exit(1) diff --git a/src/migration-scripts/sstp/1-to-2 b/src/migration-scripts/sstp/1-to-2 old mode 100755 new mode 100644 index f7ecbb6d4..2349e3c9f --- a/src/migration-scripts/sstp/1-to-2 +++ b/src/migration-scripts/sstp/1-to-2 @@ -1,110 +1,93 @@ -#!/usr/bin/env python3 +# Copyright 2020-2024 VyOS maintainers and contributors <maintainers@vyos.io> # -# Copyright (C) 2020 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 program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, +# 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 General Public License for more details. +# 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 General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. # - migrate relative path SSL certificate to absolute path, as certs are only # allowed to stored in /config/user-data/sstp/ this is pretty straight # forward move. Delete certificates from source directory import os -import sys from shutil import copy2 from stat import S_IRUSR, S_IWUSR, S_IRGRP, S_IROTH from vyos.configtree import ConfigTree -if len(sys.argv) < 2: - print("Must specify file name!") - sys.exit(1) - -file_name = sys.argv[1] +base_path = ['vpn', 'sstp', 'ssl'] -with open(file_name, 'r') as f: - config_file = f.read() +def migrate(config: ConfigTree) -> None: + if not config.exists(base_path): + # Nothing to do + return -config = ConfigTree(config_file) -base_path = ['vpn', 'sstp', 'ssl'] -if not config.exists(base_path): - # Nothing to do - sys.exit(0) -else: cert_path_old ='/config/user-data/sstp/' cert_path_new ='/config/auth/sstp/' if not os.path.isdir(cert_path_new): os.mkdir(cert_path_new) # # migrate ca-cert-file to new path if config.exists(base_path + ['ca-cert-file']): tmp = config.return_value(base_path + ['ca-cert-file']) cert_old = cert_path_old + tmp cert_new = cert_path_new + tmp if os.path.isfile(cert_old): # adjust file permissions on source file, # permissions will be copied by copy2() os.chmod(cert_old, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH) copy2(cert_old, cert_path_new) # delete old certificate file os.unlink(cert_old) config.set(base_path + ['ca-cert-file'], value=cert_new, replace=True) # # migrate cert-file to new path if config.exists(base_path + ['cert-file']): tmp = config.return_value(base_path + ['cert-file']) cert_old = cert_path_old + tmp cert_new = cert_path_new + tmp if os.path.isfile(cert_old): # adjust file permissions on source file, # permissions will be copied by copy2() os.chmod(cert_old, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH) copy2(cert_old, cert_path_new) # delete old certificate file os.unlink(cert_old) config.set(base_path + ['cert-file'], value=cert_new, replace=True) # # migrate key-file to new path if config.exists(base_path + ['key-file']): tmp = config.return_value(base_path + ['key-file']) cert_old = cert_path_old + tmp cert_new = cert_path_new + tmp if os.path.isfile(cert_old): # adjust file permissions on source file, # permissions will be copied by copy2() os.chmod(cert_old, S_IRUSR | S_IWUSR) copy2(cert_old, cert_path_new) # delete old certificate file os.unlink(cert_old) config.set(base_path + ['key-file'], value=cert_new, replace=True) # # check if old certificate directory exists but is empty if os.path.isdir(cert_path_old) and not os.listdir(cert_path_old): os.rmdir(cert_path_old) - - try: - with open(file_name, 'w') as f: - f.write(config.to_string()) - except OSError as e: - print("Failed to save the modified config: {}".format(e)) - sys.exit(1) diff --git a/src/migration-scripts/sstp/2-to-3 b/src/migration-scripts/sstp/2-to-3 old mode 100755 new mode 100644 index 245db7ad6..4255a896e --- a/src/migration-scripts/sstp/2-to-3 +++ b/src/migration-scripts/sstp/2-to-3 @@ -1,78 +1,59 @@ -#!/usr/bin/env python3 +# Copyright 2020-2024 VyOS maintainers and contributors <maintainers@vyos.io> # -# Copyright (C) 2020 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 program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, +# 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 General Public License for more details. +# 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 General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. # - Rename SSTP ppp-settings node to ppp-options to make use of a common # Jinja Template to render Accel-PPP services from vyos.configtree import ConfigTree -from sys import argv -from sys import exit - -if len(argv) < 2: - print("Must specify file name!") - exit(1) -file_name = argv[1] +base_path = ['vpn', 'sstp'] -with open(file_name, 'r') as f: - config_file = f.read() +def migrate(config: ConfigTree) -> None: + if not config.exists(base_path): + # Nothing to do + return -config = ConfigTree(config_file) -base_path = ['vpn', 'sstp'] -if not config.exists(base_path): - # Nothing to do - exit(0) -else: if config.exists(base_path + ['ppp-settings']): config.rename(base_path + ['ppp-settings'], 'ppp-options') config_ns = base_path + ['network-settings', 'name-server'] if config.exists(config_ns): config.copy(config_ns, base_path + ['name-server']) config.delete(config_ns) config_mtu = base_path + ['network-settings', 'mtu'] if config.exists(config_mtu): config.copy(config_mtu, base_path + ['mtu']) config.delete(config_mtu) config_gw = base_path + ['network-settings', 'client-ip-settings', 'gateway-address'] if config.exists(config_gw): config.copy(config_gw, base_path + ['gateway-address']) config.delete(config_gw) config_client_ip = base_path + ['network-settings', 'client-ip-settings'] if config.exists(config_client_ip): config.copy(config_client_ip, base_path + ['client-ip-pool']) config.delete(config_client_ip) config_client_ipv6 = base_path + ['network-settings', 'client-ipv6-pool'] if config.exists(config_client_ipv6): config.copy(config_client_ipv6, base_path + ['client-ipv6-pool']) config.delete(config_client_ipv6) # all nodes now have been migrated out of network-settings - delete node config_nw_settings = base_path + ['network-settings'] if config.exists(config_nw_settings): config.delete(config_nw_settings) - - try: - with open(file_name, 'w') as f: - f.write(config.to_string()) - except OSError as e: - print("Failed to save the modified config: {}".format(e)) - exit(1) - diff --git a/src/migration-scripts/sstp/3-to-4 b/src/migration-scripts/sstp/3-to-4 old mode 100755 new mode 100644 index 5b7757e60..fd10985de --- a/src/migration-scripts/sstp/3-to-4 +++ b/src/migration-scripts/sstp/3-to-4 @@ -1,135 +1,116 @@ -#!/usr/bin/env python3 +# Copyright 2021-2024 VyOS maintainers and contributors <maintainers@vyos.io> # -# Copyright (C) 2021-2024 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 program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, +# 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 General Public License for more details. +# 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 General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. # - Update SSL to use PKI configuration import os -from sys import argv -from sys import exit from vyos.configtree import ConfigTree from vyos.pki import load_certificate from vyos.pki import load_private_key from vyos.pki import encode_certificate from vyos.pki import encode_private_key from vyos.utils.process import run -if len(argv) < 2: - print("Must specify file name!") - exit(1) - -file_name = argv[1] - -with open(file_name, 'r') as f: - config_file = f.read() - -config = ConfigTree(config_file) base = ['vpn', 'sstp'] pki_base = ['pki'] -if not config.exists(base): - exit(0) - AUTH_DIR = '/config/auth' def wrapped_pem_to_config_value(pem): return "".join(pem.strip().split("\n")[1:-1]) -if not config.exists(base + ['ssl']): - exit(0) - -x509_base = base + ['ssl'] -pki_name = 'sstp' - -if not config.exists(pki_base + ['ca']): - config.set(pki_base + ['ca']) - config.set_tag(pki_base + ['ca']) - -if not config.exists(pki_base + ['certificate']): - config.set(pki_base + ['certificate']) - config.set_tag(pki_base + ['certificate']) - -if config.exists(x509_base + ['ca-cert-file']): - cert_file = config.return_value(x509_base + ['ca-cert-file']) - cert_path = os.path.join(AUTH_DIR, cert_file) - cert = None - - if os.path.isfile(cert_path): - if not os.access(cert_path, os.R_OK): - run(f'sudo chmod 644 {cert_path}') - - with open(cert_path, 'r') as f: - cert_data = f.read() - cert = load_certificate(cert_data, wrap_tags=False) - - if cert: - cert_pem = encode_certificate(cert) - config.set(pki_base + ['ca', pki_name, 'certificate'], value=wrapped_pem_to_config_value(cert_pem)) - config.set(x509_base + ['ca-certificate'], value=pki_name) - else: - print(f'Failed to migrate CA certificate on sstp config') - - config.delete(x509_base + ['ca-cert-file']) - -if config.exists(x509_base + ['cert-file']): - cert_file = config.return_value(x509_base + ['cert-file']) - cert_path = os.path.join(AUTH_DIR, cert_file) - cert = None - - if os.path.isfile(cert_path): - if not os.access(cert_path, os.R_OK): - run(f'sudo chmod 644 {cert_path}') - - with open(cert_path, 'r') as f: - cert_data = f.read() - cert = load_certificate(cert_data, wrap_tags=False) - - if cert: - cert_pem = encode_certificate(cert) - config.set(pki_base + ['certificate', pki_name, 'certificate'], value=wrapped_pem_to_config_value(cert_pem)) - config.set(x509_base + ['certificate'], value=pki_name) - else: - print(f'Failed to migrate certificate on sstp config') - - config.delete(x509_base + ['cert-file']) - -if config.exists(x509_base + ['key-file']): - key_file = config.return_value(x509_base + ['key-file']) - key_path = os.path.join(AUTH_DIR, key_file) - key = None - - if os.path.isfile(key_path): - if not os.access(key_path, os.R_OK): - run(f'sudo chmod 644 {key_path}') - - with open(key_path, 'r') as f: - key_data = f.read() - key = load_private_key(key_data, passphrase=None, wrap_tags=False) - - if key: - key_pem = encode_private_key(key, passphrase=None) - config.set(pki_base + ['certificate', pki_name, 'private', 'key'], value=wrapped_pem_to_config_value(key_pem)) - else: - print(f'Failed to migrate private key on sstp config') - - config.delete(x509_base + ['key-file']) - -try: - with open(file_name, 'w') as f: - f.write(config.to_string()) -except OSError as e: - print("Failed to save the modified config: {}".format(e)) - exit(1) +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + return + + if not config.exists(base + ['ssl']): + return + + x509_base = base + ['ssl'] + pki_name = 'sstp' + + if not config.exists(pki_base + ['ca']): + config.set(pki_base + ['ca']) + config.set_tag(pki_base + ['ca']) + + if not config.exists(pki_base + ['certificate']): + config.set(pki_base + ['certificate']) + config.set_tag(pki_base + ['certificate']) + + if config.exists(x509_base + ['ca-cert-file']): + cert_file = config.return_value(x509_base + ['ca-cert-file']) + cert_path = os.path.join(AUTH_DIR, cert_file) + cert = None + + if os.path.isfile(cert_path): + if not os.access(cert_path, os.R_OK): + run(f'sudo chmod 644 {cert_path}') + + with open(cert_path, 'r') as f: + cert_data = f.read() + cert = load_certificate(cert_data, wrap_tags=False) + + if cert: + cert_pem = encode_certificate(cert) + config.set(pki_base + ['ca', pki_name, 'certificate'], value=wrapped_pem_to_config_value(cert_pem)) + config.set(x509_base + ['ca-certificate'], value=pki_name) + else: + print(f'Failed to migrate CA certificate on sstp config') + + config.delete(x509_base + ['ca-cert-file']) + + if config.exists(x509_base + ['cert-file']): + cert_file = config.return_value(x509_base + ['cert-file']) + cert_path = os.path.join(AUTH_DIR, cert_file) + cert = None + + if os.path.isfile(cert_path): + if not os.access(cert_path, os.R_OK): + run(f'sudo chmod 644 {cert_path}') + + with open(cert_path, 'r') as f: + cert_data = f.read() + cert = load_certificate(cert_data, wrap_tags=False) + + if cert: + cert_pem = encode_certificate(cert) + config.set(pki_base + ['certificate', pki_name, 'certificate'], value=wrapped_pem_to_config_value(cert_pem)) + config.set(x509_base + ['certificate'], value=pki_name) + else: + print(f'Failed to migrate certificate on sstp config') + + config.delete(x509_base + ['cert-file']) + + if config.exists(x509_base + ['key-file']): + key_file = config.return_value(x509_base + ['key-file']) + key_path = os.path.join(AUTH_DIR, key_file) + key = None + + if os.path.isfile(key_path): + if not os.access(key_path, os.R_OK): + run(f'sudo chmod 644 {key_path}') + + with open(key_path, 'r') as f: + key_data = f.read() + key = load_private_key(key_data, passphrase=None, wrap_tags=False) + + if key: + key_pem = encode_private_key(key, passphrase=None) + config.set(pki_base + ['certificate', pki_name, 'private', 'key'], value=wrapped_pem_to_config_value(key_pem)) + else: + print(f'Failed to migrate private key on sstp config') + + config.delete(x509_base + ['key-file']) diff --git a/src/migration-scripts/sstp/4-to-5 b/src/migration-scripts/sstp/4-to-5 old mode 100755 new mode 100644 index 6907240a0..254e828af --- a/src/migration-scripts/sstp/4-to-5 +++ b/src/migration-scripts/sstp/4-to-5 @@ -1,59 +1,41 @@ -#!/usr/bin/env python3 +# Copyright 2023-2024 VyOS maintainers and contributors <maintainers@vyos.io> # -# Copyright (C) 2023-2024 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 program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, +# 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 General Public License for more details. +# 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 General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. # - move all pool to named pools # 'subnet' migrate to namedpool 'default-subnet-pool' # 'default-subnet-pool' is the next pool for 'default-range-pool' -from sys import argv -from sys import exit from vyos.configtree import ConfigTree -if len(argv) < 2: - print("Must specify file name!") - exit(1) - -file_name = argv[1] - -with open(file_name, 'r') as f: - config_file = f.read() - -config = ConfigTree(config_file) base = ['vpn', 'sstp'] pool_base = base + ['client-ip-pool'] -if not config.exists(base): - exit(0) -if not config.exists(pool_base): - exit(0) +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + return -range_pool_name = 'default-range-pool' + if not config.exists(pool_base): + return -if config.exists(pool_base + ['subnet']): - default_pool = range_pool_name - for subnet in config.return_values(pool_base + ['subnet']): - config.set(pool_base + [range_pool_name, 'range'], value=subnet, replace=False) - config.delete(pool_base + ['subnet']) - config.set(base + ['default-pool'], value=default_pool) -# format as tag node -config.set_tag(pool_base) + range_pool_name = 'default-range-pool' -try: - with open(file_name, 'w') as f: - f.write(config.to_string()) -except OSError as e: - print("Failed to save the modified config: {}".format(e)) - exit(1) + if config.exists(pool_base + ['subnet']): + default_pool = range_pool_name + for subnet in config.return_values(pool_base + ['subnet']): + config.set(pool_base + [range_pool_name, 'range'], value=subnet, replace=False) + config.delete(pool_base + ['subnet']) + config.set(base + ['default-pool'], value=default_pool) + # format as tag node + config.set_tag(pool_base) diff --git a/src/migration-scripts/sstp/5-to-6 b/src/migration-scripts/sstp/5-to-6 old mode 100755 new mode 100644 index 43b99044d..fc3cc29b2 --- a/src/migration-scripts/sstp/5-to-6 +++ b/src/migration-scripts/sstp/5-to-6 @@ -1,58 +1,40 @@ -#!/usr/bin/env python3 +# Copyright 2024 VyOS maintainers and contributors <maintainers@vyos.io> # -# Copyright (C) 2024 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 program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, +# 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 General Public License for more details. +# 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 General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. # Migrating to named ipv6 pools -from sys import argv -from sys import exit from vyos.configtree import ConfigTree -if len(argv) < 2: - print("Must specify file name!") - exit(1) +base = ['vpn', 'sstp'] +pool_base = base + ['client-ipv6-pool'] -file_name = argv[1] +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + return -with open(file_name, 'r') as f: - config_file = f.read() + if not config.exists(pool_base): + return -config = ConfigTree(config_file) -base = ['vpn', 'sstp'] -pool_base = base + ['client-ipv6-pool'] -if not config.exists(base): - exit(0) - -if not config.exists(pool_base): - exit(0) - -ipv6_pool_name = 'ipv6-pool' -config.copy(pool_base, pool_base + [ipv6_pool_name]) - -if config.exists(pool_base + ['prefix']): - config.delete(pool_base + ['prefix']) - config.set(base + ['default-ipv6-pool'], value=ipv6_pool_name) -if config.exists(pool_base + ['delegate']): - config.delete(pool_base + ['delegate']) - -# format as tag node -config.set_tag(pool_base) - -try: - with open(file_name, 'w') as f: - f.write(config.to_string()) -except OSError as e: - print("Failed to save the modified config: {}".format(e)) - exit(1) + ipv6_pool_name = 'ipv6-pool' + config.copy(pool_base, pool_base + [ipv6_pool_name]) + + if config.exists(pool_base + ['prefix']): + config.delete(pool_base + ['prefix']) + config.set(base + ['default-ipv6-pool'], value=ipv6_pool_name) + if config.exists(pool_base + ['delegate']): + config.delete(pool_base + ['delegate']) + + # format as tag node + config.set_tag(pool_base) diff --git a/src/migration-scripts/system/10-to-11 b/src/migration-scripts/system/10-to-11 old mode 100755 new mode 100644 index 5d662af40..76d7f23cb --- a/src/migration-scripts/system/10-to-11 +++ b/src/migration-scripts/system/10-to-11 @@ -1,36 +1,32 @@ -#!/usr/bin/env python3 +# Copyright 2019-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# 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 <http://www.gnu.org/licenses/>. # Operator accounts have been deprecated due to a security issue. Those accounts # will be converted to regular admin accounts. -import sys from vyos.configtree import ConfigTree -if len(sys.argv) < 2: - print("Must specify file name!") - sys.exit(1) - -file_name = sys.argv[1] - -with open(file_name, 'r') as f: - config_file = f.read() - -config = ConfigTree(config_file) base_level = ['system', 'login', 'user'] -if not config.exists(base_level): - # Nothing to do, which shouldn't happen anyway - # only if you wipe the config and reboot. - sys.exit(0) -else: +def migrate(config: ConfigTree) -> None: + if not config.exists(base_level): + # Nothing to do, which shouldn't happen anyway + # only if you wipe the config and reboot. + return + for user in config.list_nodes(base_level): if config.exists(base_level + [user, 'level']): if config.return_value(base_level + [user, 'level']) == 'operator': config.set(base_level + [user, 'level'], value="admin", replace=True) - - try: - open(file_name,'w').write(config.to_string()) - - except OSError as e: - print("Failed to save the modified config: {}".format(e)) - sys.exit(1) diff --git a/src/migration-scripts/system/11-to-12 b/src/migration-scripts/system/11-to-12 old mode 100755 new mode 100644 index 880ab56dc..71c359b7e --- a/src/migration-scripts/system/11-to-12 +++ b/src/migration-scripts/system/11-to-12 @@ -1,72 +1,69 @@ -#!/usr/bin/env python3 +# Copyright 2019-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# 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 <http://www.gnu.org/licenses/>. # Unclutter RADIUS configuration # # Move radius-server top level tag nodes to a regular node which allows us # to specify additional general features for the RADIUS client. -import sys from vyos.configtree import ConfigTree -if len(sys.argv) < 2: - print("Must specify file name!") - sys.exit(1) - -file_name = sys.argv[1] +cfg_base = ['system', 'login'] -with open(file_name, 'r') as f: - config_file = f.read() +def migrate(config: ConfigTree) -> None: + if not (config.exists(cfg_base + ['radius-server']) or config.exists(cfg_base + ['radius-source-address'])): + # Nothing to do + return -config = ConfigTree(config_file) -cfg_base = ['system', 'login'] -if not (config.exists(cfg_base + ['radius-server']) or config.exists(cfg_base + ['radius-source-address'])): - # Nothing to do - sys.exit(0) -else: # # Migrate "system login radius-source-address" to "system login radius" # if config.exists(cfg_base + ['radius-source-address']): address = config.return_value(cfg_base + ['radius-source-address']) # delete old configuration node config.delete(cfg_base + ['radius-source-address']) # write new configuration node config.set(cfg_base + ['radius', 'source-address'], value=address) # # Migrate "system login radius-server" tag node to new # "system login radius server" tag node and also rename the "secret" node to "key" # if config.exists(cfg_base + ['radius-server']): for server in config.list_nodes(cfg_base + ['radius-server']): base_server = cfg_base + ['radius-server', server] # "key" node is mandatory key = config.return_value(base_server + ['secret']) config.set(cfg_base + ['radius', 'server', server, 'key'], value=key) # "port" is optional if config.exists(base_server + ['port']): port = config.return_value(base_server + ['port']) config.set(cfg_base + ['radius', 'server', server, 'port'], value=port) # "timeout is optional" if config.exists(base_server + ['timeout']): timeout = config.return_value(base_server + ['timeout']) config.set(cfg_base + ['radius', 'server', server, 'timeout'], value=timeout) # format as tag node config.set_tag(cfg_base + ['radius', 'server']) # delete old configuration node config.delete(base_server) # delete top level tag node if config.exists(cfg_base + ['radius-server']): config.delete(cfg_base + ['radius-server']) - - try: - with open(file_name, 'w') as f: - f.write(config.to_string()) - except OSError as e: - print("Failed to save the modified config: {}".format(e)) - sys.exit(1) diff --git a/src/migration-scripts/system/12-to-13 b/src/migration-scripts/system/12-to-13 old mode 100755 new mode 100644 index e6c4e3802..014edba91 --- a/src/migration-scripts/system/12-to-13 +++ b/src/migration-scripts/system/12-to-13 @@ -1,47 +1,44 @@ -#!/usr/bin/env python3 +# Copyright 2019-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# 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 <http://www.gnu.org/licenses/>. # converts 'set system syslog host <address>:<port>' # to 'set system syslog host <address> port <port>' -import sys import re from vyos.configtree import ConfigTree -if len(sys.argv) < 2: - print("Must specify file name!") - sys.exit(1) - -file_name = sys.argv[1] - -with open(file_name, 'r') as f: - config_file = f.read() - -config = ConfigTree(config_file) cbase = ['system', 'syslog', 'host'] -if not config.exists(cbase): - sys.exit(0) - -for host in config.list_nodes(cbase): - if re.search(':[0-9]{1,5}$',host): - h = re.search('^[a-zA-Z\-0-9\.]+', host).group(0) - p = re.sub(':', '', re.search(':[0-9]+$', host).group(0)) - config.set(cbase + [h]) - config.set(cbase + [h, 'port'], value=p) - for fac in config.list_nodes(cbase + [host, 'facility']): - config.set(cbase + [h, 'facility', fac]) - config.set_tag(cbase + [h, 'facility']) - if config.exists(cbase + [host, 'facility', fac, 'protocol']): - proto = config.return_value(cbase + [host, 'facility', fac, 'protocol']) - config.set(cbase + [h, 'facility', fac, 'protocol'], value=proto) - if config.exists(cbase + [host, 'facility', fac, 'level']): - lvl = config.return_value(cbase + [host, 'facility', fac, 'level']) - config.set(cbase + [h, 'facility', fac, 'level'], value=lvl) - config.delete(cbase + [host]) - -try: - open(file_name,'w').write(config.to_string()) -except OSError as e: - print("Failed to save the modified config: {}".format(e)) - sys.exit(1) +def migrate(config: ConfigTree) -> None: + if not config.exists(cbase): + return + + for host in config.list_nodes(cbase): + if re.search(':[0-9]{1,5}$',host): + h = re.search('^[a-zA-Z\-0-9\.]+', host).group(0) + p = re.sub(':', '', re.search(':[0-9]+$', host).group(0)) + config.set(cbase + [h]) + config.set(cbase + [h, 'port'], value=p) + for fac in config.list_nodes(cbase + [host, 'facility']): + config.set(cbase + [h, 'facility', fac]) + config.set_tag(cbase + [h, 'facility']) + if config.exists(cbase + [host, 'facility', fac, 'protocol']): + proto = config.return_value(cbase + [host, 'facility', fac, 'protocol']) + config.set(cbase + [h, 'facility', fac, 'protocol'], value=proto) + if config.exists(cbase + [host, 'facility', fac, 'level']): + lvl = config.return_value(cbase + [host, 'facility', fac, 'level']) + config.set(cbase + [h, 'facility', fac, 'level'], value=lvl) + config.delete(cbase + [host]) diff --git a/src/migration-scripts/system/13-to-14 b/src/migration-scripts/system/13-to-14 old mode 100755 new mode 100644 index 5b781158b..fbbecbcd3 --- a/src/migration-scripts/system/13-to-14 +++ b/src/migration-scripts/system/13-to-14 @@ -1,70 +1,67 @@ -#!/usr/bin/env python3 +# Copyright 2019-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# 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 <http://www.gnu.org/licenses/>. # Fixup non existent time-zones. Some systems have time-zone set to: Los* # (Los_Angeles), Den* (Denver), New* (New_York) ... but those are no real IANA # assigned time zones. In the past they have been silently remapped. # # Time to clean it up! # # Migrate all configured timezones to real IANA assigned timezones! import re -import sys from vyos.configtree import ConfigTree from vyos.utils.process import cmd -if len(sys.argv) < 2: - print("Must specify file name!") - sys.exit(1) - -file_name = sys.argv[1] +tz_base = ['system', 'time-zone'] -with open(file_name, 'r') as f: - config_file = f.read() +def migrate(config: ConfigTree) -> None: + if not config.exists(tz_base): + # Nothing to do + return -config = ConfigTree(config_file) -tz_base = ['system', 'time-zone'] -if not config.exists(tz_base): - # Nothing to do - sys.exit(0) -else: tz = config.return_value(tz_base) # retrieve all valid timezones try: tz_datas = cmd('timedatectl list-timezones') except OSError: tz_datas = '' tz_data = tz_datas.split('\n') if re.match(r'[Ll][Oo][Ss].+', tz): tz = 'America/Los_Angeles' elif re.match(r'[Dd][Ee][Nn].+', tz): tz = 'America/Denver' elif re.match(r'[Hh][Oo][Nn][Oo].+', tz): tz = 'Pacific/Honolulu' elif re.match(r'[Nn][Ee][Ww].+', tz): tz = 'America/New_York' elif re.match(r'[Cc][Hh][Ii][Cc]*.+', tz): tz = 'America/Chicago' elif re.match(r'[Aa][Nn][Cc].+', tz): tz = 'America/Anchorage' elif re.match(r'[Pp][Hh][Oo].+', tz): tz = 'America/Phoenix' elif re.match(r'GMT(.+)?', tz): tz = 'Etc/' + tz elif tz not in tz_data: # assign default UTC timezone tz = 'UTC' # replace timezone data is required config.set(tz_base, value=tz) - - try: - with open(file_name, 'w') as f: - f.write(config.to_string()) - except OSError as e: - print("Failed to save the modified config: {}".format(e)) - sys.exit(1) diff --git a/src/migration-scripts/system/14-to-15 b/src/migration-scripts/system/14-to-15 old mode 100755 new mode 100644 index feaac37de..281809460 --- a/src/migration-scripts/system/14-to-15 +++ b/src/migration-scripts/system/14-to-15 @@ -1,40 +1,37 @@ -#!/usr/bin/env python3 +# Copyright 2019-2024 VyOS maintainers and contributors <maintainers@vyos.io> # +# 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 <http://www.gnu.org/licenses/>. + # Delete 'system ipv6 blacklist' option as the IPv6 module can no longer be # blacklisted as it is required by e.g. WireGuard and thus will always be # loaded. import os -import sys ipv6_blacklist_file = '/etc/modprobe.d/vyatta_blacklist_ipv6.conf' from vyos.configtree import ConfigTree -if len(sys.argv) < 2: - print("Must specify file name!") - sys.exit(1) - -file_name = sys.argv[1] +ip_base = ['system', 'ipv6'] -with open(file_name, 'r') as f: - config_file = f.read() +def migrate(config: ConfigTree) -> None: + if not config.exists(ip_base): + # Nothing to do + return -config = ConfigTree(config_file) -ip_base = ['system', 'ipv6'] -if not config.exists(ip_base): - # Nothing to do - sys.exit(0) -else: # delete 'system ipv6 blacklist' node if config.exists(ip_base + ['blacklist']): config.delete(ip_base + ['blacklist']) if os.path.isfile(ipv6_blacklist_file): os.unlink(ipv6_blacklist_file) - - try: - with open(file_name, 'w') as f: - f.write(config.to_string()) - except OSError as e: - print("Failed to save the modified config: {}".format(e)) - sys.exit(1) diff --git a/src/migration-scripts/system/15-to-16 b/src/migration-scripts/system/15-to-16 old mode 100755 new mode 100644 index 2944cdb1e..7db042930 --- a/src/migration-scripts/system/15-to-16 +++ b/src/migration-scripts/system/15-to-16 @@ -1,36 +1,32 @@ -#!/usr/bin/env python3 +# Copyright 2019-2024 VyOS maintainers and contributors <maintainers@vyos.io> # -# Make 'system options reboot-on-panic' valueless +# 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 <http://www.gnu.org/licenses/>. -import sys +# Make 'system options reboot-on-panic' valueless from vyos.configtree import ConfigTree -if len(sys.argv) < 2: - print("Must specify file name!") - sys.exit(1) - -file_name = sys.argv[1] +base = ['system', 'options'] -with open(file_name, 'r') as f: - config_file = f.read() +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + # Nothing to do + return -config = ConfigTree(config_file) -base = ['system', 'options'] -if not config.exists(base): - # Nothing to do - sys.exit(0) -else: if config.exists(base + ['reboot-on-panic']): reboot = config.return_value(base + ['reboot-on-panic']) config.delete(base + ['reboot-on-panic']) # create new valueless node if action was true if reboot == "true": config.set(base + ['reboot-on-panic']) - - try: - with open(file_name, 'w') as f: - f.write(config.to_string()) - except OSError as e: - print("Failed to save the modified config: {}".format(e)) - sys.exit(1) diff --git a/src/migration-scripts/system/16-to-17 b/src/migration-scripts/system/16-to-17 old mode 100755 new mode 100644 index afa171a9b..9fb86af88 --- a/src/migration-scripts/system/16-to-17 +++ b/src/migration-scripts/system/16-to-17 @@ -1,54 +1,36 @@ -#!/usr/bin/env python3 +# Copyright 2020-2024 VyOS maintainers and contributors <maintainers@vyos.io> # -# Copyright (C) 2020-2024 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 program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, +# 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 General Public License for more details. +# 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 General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. # * remove "system login user <user> group" node, Why should be add a user to a # 3rd party group when the system is fully managed by CLI? # * remove "system login user <user> level" node # This is the only privilege level left and also the default, what is the # sense in keeping this orphaned node? -import sys - from vyos.configtree import ConfigTree -if len(sys.argv) < 2: - print("Must specify file name!") - sys.exit(1) - -file_name = sys.argv[1] +base = ['system', 'login', 'user'] -with open(file_name, 'r') as f: - config_file = f.read() +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + # Nothing to do + return -config = ConfigTree(config_file) -base = ['system', 'login', 'user'] -if not config.exists(base): - # Nothing to do - sys.exit(0) -else: for user in config.list_nodes(base): if config.exists(base + [user, 'group']): config.delete(base + [user, 'group']) if config.exists(base + [user, 'level']): config.delete(base + [user, 'level']) - - try: - with open(file_name, 'w') as f: - f.write(config.to_string()) - except OSError as e: - print("Failed to save the modified config: {}".format(e)) - sys.exit(1) diff --git a/src/migration-scripts/system/17-to-18 b/src/migration-scripts/system/17-to-18 old mode 100755 new mode 100644 index f6adebb06..323ef4e65 --- a/src/migration-scripts/system/17-to-18 +++ b/src/migration-scripts/system/17-to-18 @@ -1,76 +1,59 @@ -#!/usr/bin/env python3 +# Copyright 2020-2024 VyOS maintainers and contributors <maintainers@vyos.io> # -# Copyright (C) 2020 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 program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, +# 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 General Public License for more details. +# 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 General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. # remove "system console netconsole" # remove "system console device <device> modem" import os -import sys from vyos.configtree import ConfigTree -if len(sys.argv) < 2: - print("Must specify file name!") - sys.exit(1) - -file_name = sys.argv[1] +base = ['system', 'console'] -with open(file_name, 'r') as f: - config_file = f.read() +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + # Nothing to do + return -config = ConfigTree(config_file) -base = ['system', 'console'] -if not config.exists(base): - # Nothing to do - sys.exit(0) -else: # remove "system console netconsole" (T2561) if config.exists(base + ['netconsole']): config.delete(base + ['netconsole']) if config.exists(base + ['device']): for device in config.list_nodes(base + ['device']): dev_path = base + ['device', device] # remove "system console device <device> modem" (T2570) if config.exists(dev_path + ['modem']): config.delete(dev_path + ['modem']) # Only continue on USB based serial consoles if not 'ttyUSB' in device: continue # A serial console has been configured but it does no longer # exist on the system - cleanup if not os.path.exists(f'/dev/{device}'): config.delete(dev_path) continue # migrate from ttyUSB device to new device in /dev/serial/by-bus for root, dirs, files in os.walk('/dev/serial/by-bus'): for usb_device in files: device_file = os.path.realpath(os.path.join(root, usb_device)) # migrate to new USB device names (T2529) if os.path.basename(device_file) == device: config.copy(dev_path, base + ['device', usb_device]) # Delete old USB node from config config.delete(dev_path) - - try: - with open(file_name, 'w') as f: - f.write(config.to_string()) - except OSError as e: - print("Failed to save the modified config: {}".format(e)) - sys.exit(1) diff --git a/src/migration-scripts/system/18-to-19 b/src/migration-scripts/system/18-to-19 old mode 100755 new mode 100644 index fad1d17a4..5d9788d70 --- a/src/migration-scripts/system/18-to-19 +++ b/src/migration-scripts/system/18-to-19 @@ -1,102 +1,81 @@ -#!/usr/bin/env python3 +# Copyright 2020-2024 VyOS maintainers and contributors <maintainers@vyos.io> # -# Copyright (C) 2020 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 program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, +# 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 General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. +# 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 <http://www.gnu.org/licenses/>. # migrate disable-dhcp-nameservers (boolean) to name-servers-dhcp <interface> # if disable-dhcp-nameservers is set, just remove it # else retrieve all interface names that have configured dhcp(v6) address and # add them to the new name-servers-dhcp node -from sys import argv, exit from vyos.ifconfig import Interface from vyos.configtree import ConfigTree -if len(argv) < 2: - print("Must specify file name!") - exit(1) - -file_name = argv[1] - -with open(file_name, 'r') as f: - config_file = f.read() - -config = ConfigTree(config_file) - base = ['system'] -if not config.exists(base): - # Nothing to do - exit(0) - -if config.exists(base + ['disable-dhcp-nameservers']): - config.delete(base + ['disable-dhcp-nameservers']) -else: - dhcp_interfaces = [] - - # go through all interfaces searching for 'address dhcp(v6)?' - for sect in Interface.sections(): - sect_base = ['interfaces', sect] - - if not config.exists(sect_base): - continue - for intf in config.list_nodes(sect_base): - intf_base = sect_base + [intf] - - # try without vlans - if config.exists(intf_base + ['address']): - for addr in config.return_values(intf_base + ['address']): - if addr in ['dhcp', 'dhcpv6']: - dhcp_interfaces.append(intf) - - # try vif - if config.exists(intf_base + ['vif']): - for vif in config.list_nodes(intf_base + ['vif']): - vif_base = intf_base + ['vif', vif] - if config.exists(vif_base + ['address']): - for addr in config.return_values(vif_base + ['address']): - if addr in ['dhcp', 'dhcpv6']: - dhcp_interfaces.append(f'{intf}.{vif}') - - # try vif-s - if config.exists(intf_base + ['vif-s']): - for vif_s in config.list_nodes(intf_base + ['vif-s']): - vif_s_base = intf_base + ['vif-s', vif_s] - if config.exists(vif_s_base + ['address']): - for addr in config.return_values(vif_s_base + ['address']): - if addr in ['dhcp', 'dhcpv6']: - dhcp_interfaces.append(f'{intf}.{vif_s}') - - # try vif-c - if config.exists(intf_base + ['vif-c']): - for vif_c in config.list_nodes(vif_s_base + ['vif-c']): - vif_c_base = vif_s_base + ['vif-c', vif_c] - if config.exists(vif_c_base + ['address']): - for addr in config.return_values(vif_c_base + ['address']): +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + # Nothing to do + return + + if config.exists(base + ['disable-dhcp-nameservers']): + config.delete(base + ['disable-dhcp-nameservers']) + else: + dhcp_interfaces = [] + + # go through all interfaces searching for 'address dhcp(v6)?' + for sect in Interface.sections(): + sect_base = ['interfaces', sect] + + if not config.exists(sect_base): + continue + + for intf in config.list_nodes(sect_base): + intf_base = sect_base + [intf] + + # try without vlans + if config.exists(intf_base + ['address']): + for addr in config.return_values(intf_base + ['address']): + if addr in ['dhcp', 'dhcpv6']: + dhcp_interfaces.append(intf) + + # try vif + if config.exists(intf_base + ['vif']): + for vif in config.list_nodes(intf_base + ['vif']): + vif_base = intf_base + ['vif', vif] + if config.exists(vif_base + ['address']): + for addr in config.return_values(vif_base + ['address']): if addr in ['dhcp', 'dhcpv6']: - dhcp_interfaces.append(f'{intf}.{vif_s}.{vif_c}') - - # set new config nodes - for intf in dhcp_interfaces: - config.set(base + ['name-servers-dhcp'], value=intf, replace=False) - -try: - with open(file_name, 'w') as f: - f.write(config.to_string()) -except OSError as e: - print("Failed to save the modified config: {}".format(e)) - exit(1) - -exit(0) + dhcp_interfaces.append(f'{intf}.{vif}') + + # try vif-s + if config.exists(intf_base + ['vif-s']): + for vif_s in config.list_nodes(intf_base + ['vif-s']): + vif_s_base = intf_base + ['vif-s', vif_s] + if config.exists(vif_s_base + ['address']): + for addr in config.return_values(vif_s_base + ['address']): + if addr in ['dhcp', 'dhcpv6']: + dhcp_interfaces.append(f'{intf}.{vif_s}') + + # try vif-c + if config.exists(intf_base + ['vif-c']): + for vif_c in config.list_nodes(vif_s_base + ['vif-c']): + vif_c_base = vif_s_base + ['vif-c', vif_c] + if config.exists(vif_c_base + ['address']): + for addr in config.return_values(vif_c_base + ['address']): + if addr in ['dhcp', 'dhcpv6']: + dhcp_interfaces.append(f'{intf}.{vif_s}.{vif_c}') + + # set new config nodes + for intf in dhcp_interfaces: + config.set(base + ['name-servers-dhcp'], value=intf, replace=False) diff --git a/src/migration-scripts/system/19-to-20 b/src/migration-scripts/system/19-to-20 old mode 100755 new mode 100644 index 177173c50..cb84e11fc --- a/src/migration-scripts/system/19-to-20 +++ b/src/migration-scripts/system/19-to-20 @@ -1,62 +1,44 @@ -#!/usr/bin/env python3 +# Copyright 2020-2024 VyOS maintainers and contributors <maintainers@vyos.io> # -# Copyright (C) 2020-2024 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 program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, +# 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 General Public License for more details. +# 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 General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. # T3048: remove smp-affinity node from ethernet and use tuned instead -from sys import exit, argv from vyos.configtree import ConfigTree -if len(argv) < 2: - print("Must specify file name!") - exit(1) - -file_name = argv[1] -with open(file_name, 'r') as f: - config_file = f.read() - base = ['system', 'options'] base_new = ['system', 'option'] -config = ConfigTree(config_file) - -if not config.exists(base): - # Nothing to do - exit(0) - -if config.exists(base_new): - for node in config.list_nodes(base): - config.copy(base + [node], base_new + [node]) -else: - config.copy(base, base_new) -config.delete(base) +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + # Nothing to do + return -# Rename "system option beep-if-fully-booted" -> "system option startup-beep" -base_beep = base_new + ['beep-if-fully-booted'] -if config.exists(base_beep): - config.rename(base_beep, 'startup-beep') + if config.exists(base_new): + for node in config.list_nodes(base): + config.copy(base + [node], base_new + [node]) + else: + config.copy(base, base_new) -# Rename "system option ctrl-alt-del-action" -> "system option ctrl-alt-delete" -base_ctrl_alt_del = base_new + ['ctrl-alt-del-action'] -if config.exists(base_ctrl_alt_del): - config.rename(base_ctrl_alt_del, 'ctrl-alt-delete') + config.delete(base) + # Rename "system option beep-if-fully-booted" -> "system option startup-beep" + base_beep = base_new + ['beep-if-fully-booted'] + if config.exists(base_beep): + config.rename(base_beep, 'startup-beep') -try: - with open(file_name, 'w') as f: - f.write(config.to_string()) -except OSError as e: - print("Failed to save the modified config: {}".format(e)) - exit(1) + # Rename "system option ctrl-alt-del-action" -> "system option ctrl-alt-delete" + base_ctrl_alt_del = base_new + ['ctrl-alt-del-action'] + if config.exists(base_ctrl_alt_del): + config.rename(base_ctrl_alt_del, 'ctrl-alt-delete') diff --git a/src/migration-scripts/system/20-to-21 b/src/migration-scripts/system/20-to-21 old mode 100755 new mode 100644 index 24e042ce2..71c283da6 --- a/src/migration-scripts/system/20-to-21 +++ b/src/migration-scripts/system/20-to-21 @@ -1,46 +1,30 @@ -#!/usr/bin/env python3 +# Copyright 2021-2024 VyOS maintainers and contributors <maintainers@vyos.io> # -# Copyright (C) 2021-2024 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 program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, +# 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 General Public License for more details. +# 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 General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. # T3795: merge "system name-servers-dhcp" into "system name-server" -from sys import argv from vyos.configtree import ConfigTree -if len(argv) < 2: - print("Must specify file name!") - exit(1) - -file_name = argv[1] -with open(file_name, 'r') as f: - config_file = f.read() - base = ['system', 'name-servers-dhcp'] -config = ConfigTree(config_file) -if not config.exists(base): - # Nothing to do - exit(0) -for interface in config.return_values(base): - config.set(['system', 'name-server'], value=interface, replace=False) +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + # Nothing to do + return -config.delete(base) + for interface in config.return_values(base): + config.set(['system', 'name-server'], value=interface, replace=False) -try: - with open(file_name, 'w') as f: - f.write(config.to_string()) -except OSError as e: - print("Failed to save the modified config: {}".format(e)) - exit(1) + config.delete(base) diff --git a/src/migration-scripts/system/21-to-22 b/src/migration-scripts/system/21-to-22 old mode 100755 new mode 100644 index 2a1b603c6..0e68a6856 --- a/src/migration-scripts/system/21-to-22 +++ b/src/migration-scripts/system/21-to-22 @@ -1,55 +1,38 @@ -#!/usr/bin/env python3 +# Copyright 2021-2024 VyOS maintainers and contributors <maintainers@vyos.io> # -# Copyright (C) 2021-2024 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 program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, +# 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 General Public License for more details. +# 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 General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. -from sys import exit, argv from vyos.configtree import ConfigTree -if len(argv) < 2: - print("Must specify file name!") - exit(1) - -file_name = argv[1] -with open(file_name, 'r') as f: - config_file = f.read() - base = ['system', 'sysctl'] -config = ConfigTree(config_file) -if not config.exists(base): - # Nothing to do - exit(0) - -for all_custom in ['all', 'custom']: - if config.exists(base + [all_custom]): - for key in config.list_nodes(base + [all_custom]): - tmp = config.return_value(base + [all_custom, key, 'value']) - config.set(base + ['parameter', key, 'value'], value=tmp) +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + # Nothing to do + return + + for all_custom in ['all', 'custom']: + if config.exists(base + [all_custom]): + for key in config.list_nodes(base + [all_custom]): + tmp = config.return_value(base + [all_custom, key, 'value']) + config.set(base + ['parameter', key, 'value'], value=tmp) + config.set_tag(base + ['parameter']) + config.delete(base + [all_custom]) + + for ipv4_param in ['net.ipv4.igmp_max_memberships', 'net.ipv4.ipfrag_time']: + if config.exists(base + [ipv4_param]): + tmp = config.return_value(base + [ipv4_param]) + config.set(base + ['parameter', ipv4_param, 'value'], value=tmp) config.set_tag(base + ['parameter']) - config.delete(base + [all_custom]) - -for ipv4_param in ['net.ipv4.igmp_max_memberships', 'net.ipv4.ipfrag_time']: - if config.exists(base + [ipv4_param]): - tmp = config.return_value(base + [ipv4_param]) - config.set(base + ['parameter', ipv4_param, 'value'], value=tmp) - config.set_tag(base + ['parameter']) - config.delete(base + [ipv4_param]) - -try: - with open(file_name, 'w') as f: - f.write(config.to_string()) -except OSError as e: - print("Failed to save the modified config: {}".format(e)) - exit(1) + config.delete(base + [ipv4_param]) diff --git a/src/migration-scripts/system/22-to-23 b/src/migration-scripts/system/22-to-23 old mode 100755 new mode 100644 index f83279b88..e49094e4a --- a/src/migration-scripts/system/22-to-23 +++ b/src/migration-scripts/system/22-to-23 @@ -1,48 +1,31 @@ -#!/usr/bin/env python3 +# Copyright 2022-2024 VyOS maintainers and contributors <maintainers@vyos.io> # -# Copyright (C) 2022-2024 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 program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, +# 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 General Public License for more details. +# 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 General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. -from sys import exit, argv from vyos.configtree import ConfigTree -if len(argv) < 2: - print("Must specify file name!") - exit(1) - -file_name = argv[1] -with open(file_name, 'r') as f: - config_file = f.read() - base = ['system', 'ipv6'] -config = ConfigTree(config_file) - -if not config.exists(base): - # Nothing to do - exit(0) -# T4346: drop support to disbale IPv6 address family within the OS Kernel -if config.exists(base + ['disable']): - config.delete(base + ['disable']) - # IPv6 address family disable was the only CLI option set - we can cleanup - # the entire tree - if len(config.list_nodes(base)) == 0: - config.delete(base) +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + # Nothing to do + return -try: - with open(file_name, 'w') as f: - f.write(config.to_string()) -except OSError as e: - print(f'Failed to save the modified config: {e}') - exit(1) + # T4346: drop support to disbale IPv6 address family within the OS Kernel + if config.exists(base + ['disable']): + config.delete(base + ['disable']) + # IPv6 address family disable was the only CLI option set - we can cleanup + # the entire tree + if len(config.list_nodes(base)) == 0: + config.delete(base) diff --git a/src/migration-scripts/system/23-to-24 b/src/migration-scripts/system/23-to-24 old mode 100755 new mode 100644 index 1fd61d83b..feb62bc32 --- a/src/migration-scripts/system/23-to-24 +++ b/src/migration-scripts/system/23-to-24 @@ -1,87 +1,71 @@ -#!/usr/bin/env python3 +# Copyright 2022-2024 VyOS maintainers and contributors <maintainers@vyos.io> # -# Copyright (C) 2022-2024 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 program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, +# 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 General Public License for more details. +# 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 General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. from ipaddress import ip_interface from ipaddress import ip_address -from sys import exit, argv + from vyos.configtree import ConfigTree from vyos.template import is_ipv4 -if len(argv) < 2: - print("Must specify file name!") - exit(1) - -file_name = argv[1] -with open(file_name, 'r') as f: - config_file = f.read() - base = ['protocols', 'static', 'arp'] tmp_base = ['protocols', 'static', 'arp-tmp'] -config = ConfigTree(config_file) -def fixup_cli(config, path, interface): +def fixup_cli(config, path, interface, host): if config.exists(path + ['address']): for address in config.return_values(path + ['address']): tmp = ip_interface(address) # ARP is only available for IPv4 ;-) if not is_ipv4(tmp): continue if ip_address(host) in tmp.network.hosts(): mac = config.return_value(tmp_base + [host, 'hwaddr']) iface_path = ['protocols', 'static', 'arp', 'interface'] config.set(iface_path + [interface, 'address', host, 'mac'], value=mac) config.set_tag(iface_path) config.set_tag(iface_path + [interface, 'address']) continue -if not config.exists(base): - # Nothing to do - exit(0) - -# We need a temporary copy of the config tree as the original one needs to be -# deleted first due to a change iun thge tagNode structure. -config.copy(base, tmp_base) -config.delete(base) +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + # Nothing to do + return -for host in config.list_nodes(tmp_base): - for type in config.list_nodes(['interfaces']): - for interface in config.list_nodes(['interfaces', type]): - if_base = ['interfaces', type, interface] - fixup_cli(config, if_base, interface) + # We need a temporary copy of the config tree as the original one needs to be + # deleted first due to a change iun thge tagNode structure. + config.copy(base, tmp_base) + config.delete(base) - if config.exists(if_base + ['vif']): - for vif in config.list_nodes(if_base + ['vif']): - vif_base = ['interfaces', type, interface, 'vif', vif] - fixup_cli(config, vif_base, f'{interface}.{vif}') + for host in config.list_nodes(tmp_base): + for type in config.list_nodes(['interfaces']): + for interface in config.list_nodes(['interfaces', type]): + if_base = ['interfaces', type, interface] + fixup_cli(config, if_base, interface, host) - if config.exists(if_base + ['vif-s']): - for vif_s in config.list_nodes(if_base + ['vif-s']): - vif_s_base = ['interfaces', type, interface, 'vif-s', vif_s] - fixup_cli(config, vif_s_base, f'{interface}.{vif_s}') + if config.exists(if_base + ['vif']): + for vif in config.list_nodes(if_base + ['vif']): + vif_base = ['interfaces', type, interface, 'vif', vif] + fixup_cli(config, vif_base, f'{interface}.{vif}', host) - if config.exists(if_base + ['vif-s', vif_s, 'vif-c']): - for vif_c in config.list_nodes(if_base + ['vif-s', vif_s, 'vif-c']): - vif_c_base = ['interfaces', type, interface, 'vif-s', vif_s, 'vif-c', vif_c] - fixup_cli(config, vif_c_base, f'{interface}.{vif_s}.{vif_c}') + if config.exists(if_base + ['vif-s']): + for vif_s in config.list_nodes(if_base + ['vif-s']): + vif_s_base = ['interfaces', type, interface, 'vif-s', vif_s] + fixup_cli(config, vif_s_base, f'{interface}.{vif_s}', host) -config.delete(tmp_base) + if config.exists(if_base + ['vif-s', vif_s, 'vif-c']): + for vif_c in config.list_nodes(if_base + ['vif-s', vif_s, 'vif-c']): + vif_c_base = ['interfaces', type, interface, 'vif-s', vif_s, 'vif-c', vif_c] + fixup_cli(config, vif_c_base, f'{interface}.{vif_s}.{vif_c}', host) -try: - with open(file_name, 'w') as f: - f.write(config.to_string()) -except OSError as e: - print(f'Failed to save the modified config: {e}') - exit(1) + config.delete(tmp_base) diff --git a/src/migration-scripts/system/24-to-25 b/src/migration-scripts/system/24-to-25 old mode 100755 new mode 100644 index 1c81a76e7..bdb89902e --- a/src/migration-scripts/system/24-to-25 +++ b/src/migration-scripts/system/24-to-25 @@ -1,52 +1,35 @@ -#!/usr/bin/env python3 +# Copyright 2022-2024 VyOS maintainers and contributors <maintainers@vyos.io> # -# Copyright (C) 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 program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, +# 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 General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. +# 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 <http://www.gnu.org/licenses/>. + # Migrate system syslog global archive to system logs logrotate messages -from sys import exit, argv from vyos.configtree import ConfigTree -if len(argv) < 2: - print("Must specify file name!") - exit(1) - -file_name = argv[1] -with open(file_name, 'r') as f: - config_file = f.read() - base = ['system', 'syslog', 'global', 'archive'] -config = ConfigTree(config_file) - -if not config.exists(base): - exit(0) -if config.exists(base + ['file']): - tmp = config.return_value(base + ['file']) - config.set(['system', 'logs', 'logrotate', 'messages', 'rotate'], value=tmp) +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + return -if config.exists(base + ['size']): - tmp = config.return_value(base + ['size']) - tmp = max(round(int(tmp) / 1024), 1) # kb -> mb - config.set(['system', 'logs', 'logrotate', 'messages', 'max-size'], value=tmp) + if config.exists(base + ['file']): + tmp = config.return_value(base + ['file']) + config.set(['system', 'logs', 'logrotate', 'messages', 'rotate'], value=tmp) -config.delete(base) + if config.exists(base + ['size']): + tmp = config.return_value(base + ['size']) + tmp = max(round(int(tmp) / 1024), 1) # kb -> mb + config.set(['system', 'logs', 'logrotate', 'messages', 'max-size'], value=tmp) -try: - with open(file_name, 'w') as f: - f.write(config.to_string()) -except OSError as e: - print(f'Failed to save the modified config: {e}') - exit(1) + config.delete(base) diff --git a/src/migration-scripts/system/25-to-26 b/src/migration-scripts/system/25-to-26 old mode 100755 new mode 100644 index 7bdf3be98..8832f48e5 --- a/src/migration-scripts/system/25-to-26 +++ b/src/migration-scripts/system/25-to-26 @@ -1,82 +1,65 @@ -#!/usr/bin/env python3 +# Copyright 2023-2024 VyOS maintainers and contributors <maintainers@vyos.io> # -# Copyright (C) 2023 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 program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, +# 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 General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. +# 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 <http://www.gnu.org/licenses/>. + # syslog: migrate deprecated CLI options # - protocols -> local7 # - security -> auth -from sys import exit, argv from vyos.configtree import ConfigTree -if len(argv) < 2: - print("Must specify file name!") - exit(1) - -file_name = argv[1] -with open(file_name, 'r') as f: - config_file = f.read() - base = ['system', 'syslog'] -config = ConfigTree(config_file) - -if not config.exists(base): - exit(0) def rename_facilities(config, base_tree, facility, facility_new) -> None: if config.exists(base + [base_tree, 'facility', facility]): # do not overwrite already existing replacement facility if not config.exists(base + [base_tree, 'facility', facility_new]): config.rename(base + [base_tree, 'facility', facility], facility_new) else: # delete old duplicate facility config config.delete(base + [base_tree, 'facility', facility]) -# -# Rename protocols and securityy facility to common ones -# -replace = { - 'protocols' : 'local7', - 'security' : 'auth' -} -for facility, facility_new in replace.items(): - rename_facilities(config, 'console', facility, facility_new) - rename_facilities(config, 'global', facility, facility_new) +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + return - if config.exists(base + ['host']): - for host in config.list_nodes(base + ['host']): - rename_facilities(config, f'host {host}', facility, facility_new) + # + # Rename protocols and securityy facility to common ones + # + replace = { + 'protocols' : 'local7', + 'security' : 'auth' + } + for facility, facility_new in replace.items(): + rename_facilities(config, 'console', facility, facility_new) + rename_facilities(config, 'global', facility, facility_new) -# -# It makes no sense to configure udp/tcp transport per individual facility -# -if config.exists(base + ['host']): - for host in config.list_nodes(base + ['host']): - protocol = None - for facility in config.list_nodes(base + ['host', host, 'facility']): - tmp_path = base + ['host', host, 'facility', facility, 'protocol'] - if config.exists(tmp_path): - # We can only change the first one - if protocol == None: - protocol = config.return_value(tmp_path) - config.set(base + ['host', host, 'protocol'], value=protocol) - config.delete(tmp_path) + if config.exists(base + ['host']): + for host in config.list_nodes(base + ['host']): + rename_facilities(config, f'host {host}', facility, facility_new) -try: - with open(file_name, 'w') as f: - f.write(config.to_string()) -except OSError as e: - print(f'Failed to save the modified config: {e}') - exit(1) + # + # It makes no sense to configure udp/tcp transport per individual facility + # + if config.exists(base + ['host']): + for host in config.list_nodes(base + ['host']): + protocol = None + for facility in config.list_nodes(base + ['host', host, 'facility']): + tmp_path = base + ['host', host, 'facility', facility, 'protocol'] + if config.exists(tmp_path): + # We can only change the first one + if protocol == None: + protocol = config.return_value(tmp_path) + config.set(base + ['host', host, 'protocol'], value=protocol) + config.delete(tmp_path) diff --git a/src/migration-scripts/system/26-to-27 b/src/migration-scripts/system/26-to-27 old mode 100755 new mode 100644 index 80bb82cbd..499e16e08 --- a/src/migration-scripts/system/26-to-27 +++ b/src/migration-scripts/system/26-to-27 @@ -1,47 +1,30 @@ -#!/usr/bin/env python3 +# Copyright 2023-2024 VyOS maintainers and contributors <maintainers@vyos.io> # -# Copyright (C) 2023 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 program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, +# 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 General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. +# 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 <http://www.gnu.org/licenses/>. + # T5877: migrate 'system domain-search domain' to 'system domain-search' -from sys import exit, argv from vyos.configtree import ConfigTree -if len(argv) < 2: - print("Must specify file name!") - exit(1) - -file_name = argv[1] -with open(file_name, 'r') as f: - config_file = f.read() - base = ['system', 'domain-search'] -config = ConfigTree(config_file) - -if not config.exists(base): - exit(0) -if config.exists(base + ['domain']): - entries = config.return_values(base + ['domain']) - config.delete(base + ['domain']) - for entry in entries: - config.set(base, value=entry, replace=False) +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + return -try: - with open(file_name, 'w') as f: - f.write(config.to_string()) -except OSError as e: - print(f'Failed to save the modified config: {e}') - exit(1) + if config.exists(base + ['domain']): + entries = config.return_values(base + ['domain']) + config.delete(base + ['domain']) + for entry in entries: + config.set(base, value=entry, replace=False) diff --git a/src/migration-scripts/system/6-to-7 b/src/migration-scripts/system/6-to-7 old mode 100755 new mode 100644 index d24521134..e91ccc4e9 --- a/src/migration-scripts/system/6-to-7 +++ b/src/migration-scripts/system/6-to-7 @@ -1,48 +1,36 @@ -#!/usr/bin/env python3 +# Copyright 2019-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# 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 <http://www.gnu.org/licenses/>. # Change smp_affinity to smp-affinity -import sys - from vyos.configtree import ConfigTree -if len(sys.argv) < 2: - print("Must specify file name!") - sys.exit(1) - -file_name = sys.argv[1] - -with open(file_name, 'r') as f: - config_file = f.read() - -config = ConfigTree(config_file) - -update_required = False - -intf_types = config.list_nodes(["interfaces"]) - -for intf_type in intf_types: - intf_type_path = ["interfaces", intf_type] - intfs = config.list_nodes(intf_type_path) - - for intf in intfs: - intf_path = intf_type_path + [intf] - if not config.exists(intf_path + ["smp_affinity"]): - # Nothing to do. - continue - else: - # Rename the node. - old_smp_affinity_path = intf_path + ["smp_affinity"] - config.rename(old_smp_affinity_path, "smp-affinity") - update_required = True - -if update_required: - try: - with open(file_name, 'w') as f: - f.write(config.to_string()) - except OSError as e: - print("failed to save the modified config: {}".format(e)) - sys.exit(1) - - - +def migrate(config: ConfigTree) -> None: + intf_types = config.list_nodes(["interfaces"]) + + for intf_type in intf_types: + intf_type_path = ["interfaces", intf_type] + intfs = config.list_nodes(intf_type_path) + + for intf in intfs: + intf_path = intf_type_path + [intf] + if not config.exists(intf_path + ["smp_affinity"]): + # Nothing to do. + continue + else: + # Rename the node. + old_smp_affinity_path = intf_path + ["smp_affinity"] + config.rename(old_smp_affinity_path, "smp-affinity") + update_required = True diff --git a/src/migration-scripts/system/7-to-8 b/src/migration-scripts/system/7-to-8 old mode 100755 new mode 100644 index 5d084d2bf..64dd4dc93 --- a/src/migration-scripts/system/7-to-8 +++ b/src/migration-scripts/system/7-to-8 @@ -1,45 +1,39 @@ -#!/usr/bin/env python3 +# Copyright 2018-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# 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 <http://www.gnu.org/licenses/>. # Converts "system gateway-address" option to "protocols static route 0.0.0.0/0 next-hop $gw" -import sys - from vyos.configtree import ConfigTree -if len(sys.argv) < 2: - print("Must specify file name!") - sys.exit(1) - -file_name = sys.argv[1] - -with open(file_name, 'r') as f: - config_file = f.read() +def migrate(config: ConfigTree) -> None: + if not config.exists(['system', 'gateway-address']): + # Nothing to do + return -config = ConfigTree(config_file) - -if not config.exists(['system', 'gateway-address']): - # Nothing to do - sys.exit(0) -else: # Save the address gw = config.return_value(['system', 'gateway-address']) # Create the node for the new syntax # Note: next-hop is a tag node, gateway address is its child, not a value config.set(['protocols', 'static', 'route', '0.0.0.0/0', 'next-hop', gw]) # Delete the node with the old syntax config.delete(['system', 'gateway-address']) # Now, the interesting part. Both route and next-hop are supposed to be tag nodes, # which you can verify with "cli-shell-api isTag $configPath". # They must be formatted as such to load correctly. config.set_tag(['protocols', 'static', 'route']) config.set_tag(['protocols', 'static', 'route', '0.0.0.0/0', 'next-hop']) - - try: - with open(file_name, 'w') as f: - f.write(config.to_string()) - except OSError as e: - print("Failed to save the modified config: {}".format(e)) - sys.exit(1) diff --git a/src/migration-scripts/system/8-to-9 b/src/migration-scripts/system/8-to-9 old mode 100755 new mode 100644 index e3bb2bca8..ea5f7af81 --- a/src/migration-scripts/system/8-to-9 +++ b/src/migration-scripts/system/8-to-9 @@ -1,32 +1,26 @@ -#!/usr/bin/env python3 +# Copyright 2018-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# 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 <http://www.gnu.org/licenses/>. # Deletes "system package" option as it is deprecated -import sys - from vyos.configtree import ConfigTree -if len(sys.argv) < 2: - print("Must specify file name!") - sys.exit(1) - -file_name = sys.argv[1] - -with open(file_name, 'r') as f: - config_file = f.read() +def migrate(config: ConfigTree) -> None: + if not config.exists(['system', 'package']): + # Nothing to do + return -config = ConfigTree(config_file) - -if not config.exists(['system', 'package']): - # Nothing to do - sys.exit(0) -else: # Delete the node with the old syntax config.delete(['system', 'package']) - - try: - with open(file_name, 'w') as f: - f.write(config.to_string()) - except OSError as e: - print("Failed to save the modified config: {}".format(e)) - sys.exit(1) diff --git a/src/migration-scripts/vrf/0-to-1 b/src/migration-scripts/vrf/0-to-1 old mode 100755 new mode 100644 index 8187138d9..70abae2a8 --- a/src/migration-scripts/vrf/0-to-1 +++ b/src/migration-scripts/vrf/0-to-1 @@ -1,132 +1,113 @@ -#!/usr/bin/env python3 +# Copyright 2021-2024 VyOS maintainers and contributors <maintainers@vyos.io> # -# Copyright (C) 2021 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 program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, +# 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 General Public License for more details. +# 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 General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. # - T2450: drop interface-route and interface-route6 from "protocols vrf" -from sys import argv -from sys import exit from vyos.configtree import ConfigTree -if len(argv) < 2: - print("Must specify file name!") - exit(1) - -file_name = argv[1] - -with open(file_name, 'r') as f: - config_file = f.read() - base = ['protocols', 'vrf'] -config = ConfigTree(config_file) - -if not config.exists(base): - # Nothing to do - exit(0) - -for vrf in config.list_nodes(base): - static_base = base + [vrf, 'static'] - if not config.exists(static_base): - continue - - # - # Migrate interface-route into route - # - interface_route_path = static_base + ['interface-route'] - if config.exists(interface_route_path): - for route in config.list_nodes(interface_route_path): - interface = config.list_nodes(interface_route_path + [route, 'next-hop-interface']) - - tmp = interface_route_path + [route, 'next-hop-interface'] - for interface in config.list_nodes(tmp): - new_base = static_base + ['route', route, 'interface'] - config.set(new_base) - config.set_tag(new_base) - config.copy(tmp + [interface], new_base + [interface]) - - config.delete(interface_route_path) - - # - # Migrate interface-route6 into route6 - # - interface_route_path = static_base + ['interface-route6'] - if config.exists(interface_route_path): - for route in config.list_nodes(interface_route_path): - interface = config.list_nodes(interface_route_path + [route, 'next-hop-interface']) - - tmp = interface_route_path + [route, 'next-hop-interface'] - for interface in config.list_nodes(tmp): - new_base = static_base + ['route6', route, 'interface'] - config.set(new_base) - config.set_tag(new_base) - config.copy(tmp + [interface], new_base + [interface]) - - config.delete(interface_route_path) - - # - # Cleanup nodes inside route - # - route_path = static_base + ['route'] - if config.exists(route_path): - for route in config.list_nodes(route_path): - next_hop = route_path + [route, 'next-hop'] - if config.exists(next_hop): - for gateway in config.list_nodes(next_hop): - interface_path = next_hop + [gateway, 'next-hop-interface'] - if config.exists(interface_path): - config.rename(interface_path, 'interface') - vrf_path = next_hop + [gateway, 'next-hop-vrf'] - if config.exists(vrf_path): - config.rename(vrf_path, 'vrf') - - next_hop = route_path + [route, 'interface'] - if config.exists(next_hop): - for interface in config.list_nodes(next_hop): - interface_path = next_hop + [interface, 'next-hop-interface'] - if config.exists(interface_path): - config.rename(interface_path, 'interface') - vrf_path = next_hop + [interface, 'next-hop-vrf'] - if config.exists(vrf_path): - config.rename(vrf_path, 'vrf') - - # - # Cleanup nodes inside route6 - # - route_path = static_base + ['route6'] - if config.exists(route_path): - for route in config.list_nodes(route_path): - next_hop = route_path + [route, 'next-hop'] - if config.exists(next_hop): - for gateway in config.list_nodes(next_hop): - vrf_path = next_hop + [gateway, 'next-hop-vrf'] - if config.exists(vrf_path): - config.rename(vrf_path, 'vrf') - - next_hop = route_path + [route, 'interface'] - if config.exists(next_hop): - for interface in config.list_nodes(next_hop): - interface_path = next_hop + [interface, 'next-hop-interface'] - if config.exists(interface_path): - config.rename(interface_path, 'interface') - vrf_path = next_hop + [interface, 'next-hop-vrf'] - if config.exists(vrf_path): - config.rename(vrf_path, 'vrf') -try: - with open(file_name, 'w') as f: - f.write(config.to_string()) -except OSError as e: - print("Failed to save the modified config: {}".format(e)) - exit(1) +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + # Nothing to do + return + + for vrf in config.list_nodes(base): + static_base = base + [vrf, 'static'] + if not config.exists(static_base): + continue + + # + # Migrate interface-route into route + # + interface_route_path = static_base + ['interface-route'] + if config.exists(interface_route_path): + for route in config.list_nodes(interface_route_path): + interface = config.list_nodes(interface_route_path + [route, 'next-hop-interface']) + + tmp = interface_route_path + [route, 'next-hop-interface'] + for interface in config.list_nodes(tmp): + new_base = static_base + ['route', route, 'interface'] + config.set(new_base) + config.set_tag(new_base) + config.copy(tmp + [interface], new_base + [interface]) + + config.delete(interface_route_path) + + # + # Migrate interface-route6 into route6 + # + interface_route_path = static_base + ['interface-route6'] + if config.exists(interface_route_path): + for route in config.list_nodes(interface_route_path): + interface = config.list_nodes(interface_route_path + [route, 'next-hop-interface']) + + tmp = interface_route_path + [route, 'next-hop-interface'] + for interface in config.list_nodes(tmp): + new_base = static_base + ['route6', route, 'interface'] + config.set(new_base) + config.set_tag(new_base) + config.copy(tmp + [interface], new_base + [interface]) + + config.delete(interface_route_path) + + # + # Cleanup nodes inside route + # + route_path = static_base + ['route'] + if config.exists(route_path): + for route in config.list_nodes(route_path): + next_hop = route_path + [route, 'next-hop'] + if config.exists(next_hop): + for gateway in config.list_nodes(next_hop): + interface_path = next_hop + [gateway, 'next-hop-interface'] + if config.exists(interface_path): + config.rename(interface_path, 'interface') + vrf_path = next_hop + [gateway, 'next-hop-vrf'] + if config.exists(vrf_path): + config.rename(vrf_path, 'vrf') + + next_hop = route_path + [route, 'interface'] + if config.exists(next_hop): + for interface in config.list_nodes(next_hop): + interface_path = next_hop + [interface, 'next-hop-interface'] + if config.exists(interface_path): + config.rename(interface_path, 'interface') + vrf_path = next_hop + [interface, 'next-hop-vrf'] + if config.exists(vrf_path): + config.rename(vrf_path, 'vrf') + + # + # Cleanup nodes inside route6 + # + route_path = static_base + ['route6'] + if config.exists(route_path): + for route in config.list_nodes(route_path): + next_hop = route_path + [route, 'next-hop'] + if config.exists(next_hop): + for gateway in config.list_nodes(next_hop): + vrf_path = next_hop + [gateway, 'next-hop-vrf'] + if config.exists(vrf_path): + config.rename(vrf_path, 'vrf') + + next_hop = route_path + [route, 'interface'] + if config.exists(next_hop): + for interface in config.list_nodes(next_hop): + interface_path = next_hop + [interface, 'next-hop-interface'] + if config.exists(interface_path): + config.rename(interface_path, 'interface') + vrf_path = next_hop + [interface, 'next-hop-vrf'] + if config.exists(vrf_path): + config.rename(vrf_path, 'vrf') diff --git a/src/migration-scripts/vrf/1-to-2 b/src/migration-scripts/vrf/1-to-2 old mode 100755 new mode 100644 index 52d4c2c7b..557a9ec58 --- a/src/migration-scripts/vrf/1-to-2 +++ b/src/migration-scripts/vrf/1-to-2 @@ -1,62 +1,43 @@ -#!/usr/bin/env python3 +# Copyright 2021-2024 VyOS maintainers and contributors <maintainers@vyos.io> # -# Copyright (C) 2021 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 program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, +# 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 General Public License for more details. +# 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 General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. # - T3344: migrate routing options from "protocols vrf" to "vrf <name> protocols" -from sys import argv -from sys import exit from vyos.configtree import ConfigTree -if len(argv) < 2: - print("Must specify file name!") - exit(1) +base = ['protocols', 'vrf'] -file_name = argv[1] +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + # Nothing to do + return -with open(file_name, 'r') as f: - config_file = f.read() + vrf_base = ['vrf', 'name'] + config.set(vrf_base) + config.set_tag(vrf_base) -base = ['protocols', 'vrf'] -config = ConfigTree(config_file) - -if not config.exists(base): - # Nothing to do - exit(0) - -vrf_base = ['vrf', 'name'] -config.set(vrf_base) -config.set_tag(vrf_base) - -# Copy all existing static routes to the new base node under "vrf name <name> protocols static" -for vrf in config.list_nodes(base): - static_base = base + [vrf, 'static'] - if not config.exists(static_base): - continue - - new_static_base = vrf_base + [vrf, 'protocols'] - config.set(new_static_base) - config.copy(static_base, new_static_base + ['static']) - config.set_tag(new_static_base + ['static', 'route']) - -# Now delete the old configuration -config.delete(base) - -try: - with open(file_name, 'w') as f: - f.write(config.to_string()) -except OSError as e: - print("Failed to save the modified config: {}".format(e)) - exit(1) + # Copy all existing static routes to the new base node under "vrf name <name> protocols static" + for vrf in config.list_nodes(base): + static_base = base + [vrf, 'static'] + if not config.exists(static_base): + continue + + new_static_base = vrf_base + [vrf, 'protocols'] + config.set(new_static_base) + config.copy(static_base, new_static_base + ['static']) + config.set_tag(new_static_base + ['static', 'route']) + + # Now delete the old configuration + config.delete(base) diff --git a/src/migration-scripts/vrf/2-to-3 b/src/migration-scripts/vrf/2-to-3 old mode 100755 new mode 100644 index d45b185ee..acacffb41 --- a/src/migration-scripts/vrf/2-to-3 +++ b/src/migration-scripts/vrf/2-to-3 @@ -1,144 +1,125 @@ -#!/usr/bin/env python3 +# Copyright 2021-2024 VyOS maintainers and contributors <maintainers@vyos.io> # -# Copyright (C) 2021 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 program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, +# 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 General Public License for more details. +# 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 General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. # Since connection tracking zones are int16, VRFs tables maximum value must # be limited to 65535 # Also, interface names in nftables cannot start from numbers, # so VRF name should not start from a number -from sys import argv -from sys import exit from random import randrange from random import choice from string import ascii_lowercase from vyos.configtree import ConfigTree import re # Helper function to find all config items with a VRF name def _search_vrfs(config_commands, vrf_name): vrf_values = [] # Regex to find path of config command with old VRF regex_filter = re.compile(rf'^set (?P<cmd_path>[^\']+vrf) \'{vrf_name}\'$') # Check each command for VRF value for config_command in config_commands: search_result = regex_filter.search(config_command) if search_result: # Append VRF command to a list vrf_values.append(search_result.group('cmd_path').split()) if vrf_values: return vrf_values else: return None # Helper function to find all config items with a table number def _search_tables(config_commands, table_num): table_items = {'table_tags': [], 'table_values': []} # Regex to find values and nodes with a table number regex_tags = re.compile(rf'^set (?P<cmd_path>[^\']+table {table_num}) ?.*$') regex_values = re.compile( rf'^set (?P<cmd_path>[^\']+table) \'{table_num}\'$') for config_command in config_commands: # Search for tag nodes search_result = regex_tags.search(config_command) if search_result: # Append table node path to a tag nodes list cmd_path = search_result.group('cmd_path').split() if cmd_path not in table_items['table_tags']: table_items['table_tags'].append(cmd_path) # Search for value nodes search_result = regex_values.search(config_command) if search_result: # Append table node path to a value nodes list table_items['table_values'].append( search_result.group('cmd_path').split()) return table_items -if len(argv) < 2: - print("Must specify file name!") - exit(1) - -file_name = argv[1] - -with open(file_name, 'r') as f: - config_file = f.read() - base = ['vrf', 'name'] -config = ConfigTree(config_file) - -if not config.exists(base): - # Nothing to do - exit(0) - -# Get a list of all currently used VRFs and tables -vrfs_current = {} -for vrf in config.list_nodes(base): - vrfs_current[vrf] = int(config.return_value(base + [vrf, 'table'])) - -# Check VRF names and table numbers -name_regex = re.compile(r'^\d.*$') -for vrf_name, vrf_table in vrfs_current.items(): - # Check table number - if vrf_table > 65535: - # Find new unused table number - vrfs_current[vrf_name] = None - while not vrfs_current[vrf_name]: - table_random = randrange(100, 65535) - if table_random not in vrfs_current.values(): - vrfs_current[vrf_name] = table_random - # Update number to a new one - config.set(['vrf', 'name', vrf_name, 'table'], - vrfs_current[vrf_name], - replace=True) - # Check config items with old table number and replace to new one - config_commands = config.to_commands().split('\n') - table_config_lines = _search_tables(config_commands, vrf_table) - # Rename table nodes - if table_config_lines.get('table_tags'): - for table_config_path in table_config_lines.get('table_tags'): - config.rename(table_config_path, f'{vrfs_current[vrf_name]}') - # Replace table values - if table_config_lines.get('table_values'): - for table_config_path in table_config_lines.get('table_values'): - config.set(table_config_path, - f'{vrfs_current[vrf_name]}', - replace=True) - - # Check VRF name - if name_regex.match(vrf_name): - vrf_name_new = None - while not vrf_name_new: - vrf_name_rand = f'{choice(ascii_lowercase)}{vrf_name}'[:15] - if vrf_name_rand not in vrfs_current: - vrf_name_new = vrf_name_rand - # Update VRF name to a new one - config.rename(['vrf', 'name', vrf_name], vrf_name_new) - # Check config items with old VRF name and replace to new one - config_commands = config.to_commands().split('\n') - vrf_config_lines = _search_vrfs(config_commands, vrf_name) - # Rename VRF to a new name - if vrf_config_lines: - for vrf_value_path in vrf_config_lines: - config.set(vrf_value_path, vrf_name_new, replace=True) -try: - with open(file_name, 'w') as f: - f.write(config.to_string()) -except OSError as e: - print("Failed to save the modified config: {}".format(e)) - exit(1) +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + # Nothing to do + return + + # Get a list of all currently used VRFs and tables + vrfs_current = {} + for vrf in config.list_nodes(base): + vrfs_current[vrf] = int(config.return_value(base + [vrf, 'table'])) + + # Check VRF names and table numbers + name_regex = re.compile(r'^\d.*$') + for vrf_name, vrf_table in vrfs_current.items(): + # Check table number + if vrf_table > 65535: + # Find new unused table number + vrfs_current[vrf_name] = None + while not vrfs_current[vrf_name]: + table_random = randrange(100, 65535) + if table_random not in vrfs_current.values(): + vrfs_current[vrf_name] = table_random + # Update number to a new one + config.set(['vrf', 'name', vrf_name, 'table'], + vrfs_current[vrf_name], + replace=True) + # Check config items with old table number and replace to new one + config_commands = config.to_commands().split('\n') + table_config_lines = _search_tables(config_commands, vrf_table) + # Rename table nodes + if table_config_lines.get('table_tags'): + for table_config_path in table_config_lines.get('table_tags'): + config.rename(table_config_path, f'{vrfs_current[vrf_name]}') + # Replace table values + if table_config_lines.get('table_values'): + for table_config_path in table_config_lines.get('table_values'): + config.set(table_config_path, + f'{vrfs_current[vrf_name]}', + replace=True) + + # Check VRF name + if name_regex.match(vrf_name): + vrf_name_new = None + while not vrf_name_new: + vrf_name_rand = f'{choice(ascii_lowercase)}{vrf_name}'[:15] + if vrf_name_rand not in vrfs_current: + vrf_name_new = vrf_name_rand + # Update VRF name to a new one + config.rename(['vrf', 'name', vrf_name], vrf_name_new) + # Check config items with old VRF name and replace to new one + config_commands = config.to_commands().split('\n') + vrf_config_lines = _search_vrfs(config_commands, vrf_name) + # Rename VRF to a new name + if vrf_config_lines: + for vrf_value_path in vrf_config_lines: + config.set(vrf_value_path, vrf_name_new, replace=True) diff --git a/src/migration-scripts/vrrp/1-to-2 b/src/migration-scripts/vrrp/1-to-2 old mode 100755 new mode 100644 index dba5af81c..8639a7553 --- a/src/migration-scripts/vrrp/1-to-2 +++ b/src/migration-scripts/vrrp/1-to-2 @@ -1,270 +1,250 @@ -#!/usr/bin/env python3 +# Copyright 2018-2024 VyOS maintainers and contributors <maintainers@vyos.io> # -# 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 program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, +# 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 General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. +# 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 <http://www.gnu.org/licenses/>. import re -import sys from vyos.configtree import ConfigTree -if len(sys.argv) < 2: - print("Must specify file name!") - sys.exit(1) - -file_name = sys.argv[1] - -with open(file_name, 'r') as f: - config_file = f.read() - -config = ConfigTree(config_file) - # Convert the old VRRP syntax to the new syntax # The old approach was to put VRRP groups inside interfaces, # as in "interfaces ethernet eth0 vrrp vrrp-group 10 ...". # It was supported only under ethernet and bonding and their # respective vif, vif-s, and vif-c subinterfaces def get_vrrp_group(path): group = {"preempt": True, "rfc_compatibility": False, "disable": False} if config.exists(path + ["advertise-interval"]): group["advertise_interval"] = config.return_value(path + ["advertise-interval"]) if config.exists(path + ["description"]): group["description"] = config.return_value(path + ["description"]) if config.exists(path + ["disable"]): group["disable"] = True if config.exists(path + ["hello-source-address"]): group["hello_source"] = config.return_value(path + ["hello-source-address"]) # 1.1.8 didn't have it, but earlier 1.2.0 did, we don't want to break # configs of early adopters! if config.exists(path + ["peer-address"]): group["peer_address"] = config.return_value(path + ["peer-address"]) if config.exists(path + ["preempt"]): preempt = config.return_value(path + ["preempt"]) if preempt == "false": group["preempt"] = False if config.exists(path + ["rfc3768-compatibility"]): group["rfc_compatibility"] = True if config.exists(path + ["preempt-delay"]): group["preempt_delay"] = config.return_value(path + ["preempt-delay"]) if config.exists(path + ["priority"]): group["priority"] = config.return_value(path + ["priority"]) if config.exists(path + ["sync-group"]): group["sync_group"] = config.return_value(path + ["sync-group"]) if config.exists(path + ["authentication", "type"]): group["auth_type"] = config.return_value(path + ["authentication", "type"]) if config.exists(path + ["authentication", "password"]): group["auth_password"] = config.return_value(path + ["authentication", "password"]) if config.exists(path + ["virtual-address"]): group["virtual_addresses"] = config.return_values(path + ["virtual-address"]) if config.exists(path + ["run-transition-scripts"]): if config.exists(path + ["run-transition-scripts", "master"]): group["master_script"] = config.return_value(path + ["run-transition-scripts", "master"]) if config.exists(path + ["run-transition-scripts", "backup"]): group["backup_script"] = config.return_value(path + ["run-transition-scripts", "backup"]) if config.exists(path + ["run-transition-scripts", "fault"]): group["fault_script"] = config.return_value(path + ["run-transition-scripts", "fault"]) # Also not present in 1.1.8, but supported by earlier 1.2.0 if config.exists(path + ["health-check"]): if config.exists(path + ["health-check", "interval"]): group["health_check_interval"] = config.return_value(path + ["health-check", "interval"]) if config.exists(path + ["health-check", "failure-count"]): group["health_check_count"] = config.return_value(path + ["health-check", "failure-count"]) if config.exists(path + ["health-check", "script"]): group["health_check_script"] = config.return_value(path + ["health-check", "script"]) return group # Since VRRP is all over the place, there's no way to just check a path and exit early # if it doesn't exist, we have to walk all interfaces and collect VRRP settings from them. # Only if no data is collected from any interface we can conclude that VRRP is not configured # and exit. -groups = [] -base_paths = [] - -if config.exists(["interfaces", "ethernet"]): - base_paths.append("ethernet") -if config.exists(["interfaces", "bonding"]): - base_paths.append("bonding") - -for bp in base_paths: - parent_path = ["interfaces", bp] - - parent_intfs = config.list_nodes(parent_path) - - for pi in parent_intfs: - # Extract VRRP groups from the parent interface - vg_path =[pi, "vrrp", "vrrp-group"] - if config.exists(parent_path + vg_path): - pgroups = config.list_nodes(parent_path + vg_path) - for pg in pgroups: - g = get_vrrp_group(parent_path + vg_path + [pg]) - g["interface"] = pi - g["vrid"] = pg - groups.append(g) - - # Delete the VRRP subtree - # If left in place, configs will not load correctly - config.delete(parent_path + [pi, "vrrp"]) - - # Extract VRRP groups from 802.1q VLAN interfaces - if config.exists(parent_path + [pi, "vif"]): - vifs = config.list_nodes(parent_path + [pi, "vif"]) - for vif in vifs: - vif_vg_path = [pi, "vif", vif, "vrrp", "vrrp-group"] - if config.exists(parent_path + vif_vg_path): - vifgroups = config.list_nodes(parent_path + vif_vg_path) - for vif_group in vifgroups: - g = get_vrrp_group(parent_path + vif_vg_path + [vif_group]) - g["interface"] = "{0}.{1}".format(pi, vif) - g["vrid"] = vif_group - groups.append(g) - - config.delete(parent_path + [pi, "vif", vif, "vrrp"]) - - # Extract VRRP groups from 802.3ad QinQ service VLAN interfaces - if config.exists(parent_path + [pi, "vif-s"]): - vif_ss = config.list_nodes(parent_path + [pi, "vif-s"]) - for vif_s in vif_ss: - vifs_vg_path = [pi, "vif-s", vif_s, "vrrp", "vrrp-group"] - if config.exists(parent_path + vifs_vg_path): - vifsgroups = config.list_nodes(parent_path + vifs_vg_path) - for vifs_group in vifsgroups: - g = get_vrrp_group(parent_path + vifs_vg_path + [vifs_group]) - g["interface"] = "{0}.{1}".format(pi, vif_s) - g["vrid"] = vifs_group - groups.append(g) - - config.delete(parent_path + [pi, "vif-s", vif_s, "vrrp"]) - - # Extract VRRP groups from QinQ client VLAN interfaces nested in the vif-s - if config.exists(parent_path + [pi, "vif-s", vif_s, "vif-c"]): - vif_cs = config.list_nodes(parent_path + [pi, "vif-s", vif_s, "vif-c"]) - for vif_c in vif_cs: - vifc_vg_path = [pi, "vif-s", vif_s, "vif-c", vif_c, "vrrp", "vrrp-group"] - vifcgroups = config.list_nodes(parent_path + vifc_vg_path) - for vifc_group in vifcgroups: - g = get_vrrp_group(parent_path + vifc_vg_path + [vifc_group]) - g["interface"] = "{0}.{1}.{2}".format(pi, vif_s, vif_c) - g["vrid"] = vifc_group - groups.append(g) - - config.delete(parent_path + [pi, "vif-s", vif_s, "vif-c", vif_c, "vrrp"]) - -# If nothing was collected before this point, it means the config has no VRRP setup -if not groups: - sys.exit(0) - -# Otherwise, there is VRRP to convert - -# Now convert the collected groups to the new syntax -base_group_path = ["high-availability", "vrrp", "group"] -sync_path = ["high-availability", "vrrp", "sync-group"] - -for g in groups: - group_name = "{0}-{1}".format(g["interface"], g["vrid"]) - group_path = base_group_path + [group_name] - - config.set(group_path + ["interface"], value=g["interface"]) - config.set(group_path + ["vrid"], value=g["vrid"]) - - if "advertise_interval" in g: - config.set(group_path + ["advertise-interval"], value=g["advertise_interval"]) - - if "priority" in g: - config.set(group_path + ["priority"], value=g["priority"]) - - if not g["preempt"]: - config.set(group_path + ["no-preempt"], value=None) - - if "preempt_delay" in g: - config.set(group_path + ["preempt-delay"], value=g["preempt_delay"]) - - if g["rfc_compatibility"]: - config.set(group_path + ["rfc3768-compatibility"], value=None) - - if g["disable"]: - config.set(group_path + ["disable"], value=None) - - if "hello_source" in g: - config.set(group_path + ["hello-source-address"], value=g["hello_source"]) - - if "peer_address" in g: - config.set(group_path + ["peer-address"], value=g["peer_address"]) - - if "auth_password" in g: - config.set(group_path + ["authentication", "password"], value=g["auth_password"]) - if "auth_type" in g: - config.set(group_path + ["authentication", "type"], value=g["auth_type"]) - - if "master_script" in g: - config.set(group_path + ["transition-script", "master"], value=g["master_script"]) - if "backup_script" in g: - config.set(group_path + ["transition-script", "backup"], value=g["backup_script"]) - if "fault_script" in g: - config.set(group_path + ["transition-script", "fault"], value=g["fault_script"]) - - if "health_check_interval" in g: - config.set(group_path + ["health-check", "interval"], value=g["health_check_interval"]) - if "health_check_count" in g: - config.set(group_path + ["health-check", "failure-count"], value=g["health_check_count"]) - if "health_check_script" in g: - config.set(group_path + ["health-check", "script"], value=g["health_check_script"]) - - # Not that it should ever be absent... - if "virtual_addresses" in g: - # The new CLI disallows addresses without prefix length - # Pre-rewrite configs didn't support IPv6 VRRP, but handle it anyway - for va in g["virtual_addresses"]: - if not re.search(r'/', va): - if re.search(r':', va): - va = "{0}/128".format(va) - else: - va = "{0}/32".format(va) - config.set(group_path + ["virtual-address"], value=va, replace=False) - - # Sync group - if "sync_group" in g: - config.set(sync_path + [g["sync_group"], "member"], value=group_name, replace=False) - -# Set the tag flag -config.set_tag(base_group_path) -if config.exists(sync_path): - config.set_tag(sync_path) - -try: - with open(file_name, 'w') as f: - f.write(config.to_string()) -except OSError as e: - print("Failed to save the modified config: {}".format(e)) - sys.exit(1) +def migrate(config: ConfigTree) -> None: + groups = [] + base_paths = [] + + if config.exists(["interfaces", "ethernet"]): + base_paths.append("ethernet") + if config.exists(["interfaces", "bonding"]): + base_paths.append("bonding") + + for bp in base_paths: + parent_path = ["interfaces", bp] + + parent_intfs = config.list_nodes(parent_path) + + for pi in parent_intfs: + # Extract VRRP groups from the parent interface + vg_path =[pi, "vrrp", "vrrp-group"] + if config.exists(parent_path + vg_path): + pgroups = config.list_nodes(parent_path + vg_path) + for pg in pgroups: + g = get_vrrp_group(parent_path + vg_path + [pg]) + g["interface"] = pi + g["vrid"] = pg + groups.append(g) + + # Delete the VRRP subtree + # If left in place, configs will not load correctly + config.delete(parent_path + [pi, "vrrp"]) + + # Extract VRRP groups from 802.1q VLAN interfaces + if config.exists(parent_path + [pi, "vif"]): + vifs = config.list_nodes(parent_path + [pi, "vif"]) + for vif in vifs: + vif_vg_path = [pi, "vif", vif, "vrrp", "vrrp-group"] + if config.exists(parent_path + vif_vg_path): + vifgroups = config.list_nodes(parent_path + vif_vg_path) + for vif_group in vifgroups: + g = get_vrrp_group(parent_path + vif_vg_path + [vif_group]) + g["interface"] = "{0}.{1}".format(pi, vif) + g["vrid"] = vif_group + groups.append(g) + + config.delete(parent_path + [pi, "vif", vif, "vrrp"]) + + # Extract VRRP groups from 802.3ad QinQ service VLAN interfaces + if config.exists(parent_path + [pi, "vif-s"]): + vif_ss = config.list_nodes(parent_path + [pi, "vif-s"]) + for vif_s in vif_ss: + vifs_vg_path = [pi, "vif-s", vif_s, "vrrp", "vrrp-group"] + if config.exists(parent_path + vifs_vg_path): + vifsgroups = config.list_nodes(parent_path + vifs_vg_path) + for vifs_group in vifsgroups: + g = get_vrrp_group(parent_path + vifs_vg_path + [vifs_group]) + g["interface"] = "{0}.{1}".format(pi, vif_s) + g["vrid"] = vifs_group + groups.append(g) + + config.delete(parent_path + [pi, "vif-s", vif_s, "vrrp"]) + + # Extract VRRP groups from QinQ client VLAN interfaces nested in the vif-s + if config.exists(parent_path + [pi, "vif-s", vif_s, "vif-c"]): + vif_cs = config.list_nodes(parent_path + [pi, "vif-s", vif_s, "vif-c"]) + for vif_c in vif_cs: + vifc_vg_path = [pi, "vif-s", vif_s, "vif-c", vif_c, "vrrp", "vrrp-group"] + vifcgroups = config.list_nodes(parent_path + vifc_vg_path) + for vifc_group in vifcgroups: + g = get_vrrp_group(parent_path + vifc_vg_path + [vifc_group]) + g["interface"] = "{0}.{1}.{2}".format(pi, vif_s, vif_c) + g["vrid"] = vifc_group + groups.append(g) + + config.delete(parent_path + [pi, "vif-s", vif_s, "vif-c", vif_c, "vrrp"]) + + # If nothing was collected before this point, it means the config has no VRRP setup + if not groups: + return + + # Otherwise, there is VRRP to convert + + # Now convert the collected groups to the new syntax + base_group_path = ["high-availability", "vrrp", "group"] + sync_path = ["high-availability", "vrrp", "sync-group"] + + for g in groups: + group_name = "{0}-{1}".format(g["interface"], g["vrid"]) + group_path = base_group_path + [group_name] + + config.set(group_path + ["interface"], value=g["interface"]) + config.set(group_path + ["vrid"], value=g["vrid"]) + + if "advertise_interval" in g: + config.set(group_path + ["advertise-interval"], value=g["advertise_interval"]) + + if "priority" in g: + config.set(group_path + ["priority"], value=g["priority"]) + + if not g["preempt"]: + config.set(group_path + ["no-preempt"], value=None) + + if "preempt_delay" in g: + config.set(group_path + ["preempt-delay"], value=g["preempt_delay"]) + + if g["rfc_compatibility"]: + config.set(group_path + ["rfc3768-compatibility"], value=None) + + if g["disable"]: + config.set(group_path + ["disable"], value=None) + + if "hello_source" in g: + config.set(group_path + ["hello-source-address"], value=g["hello_source"]) + + if "peer_address" in g: + config.set(group_path + ["peer-address"], value=g["peer_address"]) + + if "auth_password" in g: + config.set(group_path + ["authentication", "password"], value=g["auth_password"]) + if "auth_type" in g: + config.set(group_path + ["authentication", "type"], value=g["auth_type"]) + + if "master_script" in g: + config.set(group_path + ["transition-script", "master"], value=g["master_script"]) + if "backup_script" in g: + config.set(group_path + ["transition-script", "backup"], value=g["backup_script"]) + if "fault_script" in g: + config.set(group_path + ["transition-script", "fault"], value=g["fault_script"]) + + if "health_check_interval" in g: + config.set(group_path + ["health-check", "interval"], value=g["health_check_interval"]) + if "health_check_count" in g: + config.set(group_path + ["health-check", "failure-count"], value=g["health_check_count"]) + if "health_check_script" in g: + config.set(group_path + ["health-check", "script"], value=g["health_check_script"]) + + # Not that it should ever be absent... + if "virtual_addresses" in g: + # The new CLI disallows addresses without prefix length + # Pre-rewrite configs didn't support IPv6 VRRP, but handle it anyway + for va in g["virtual_addresses"]: + if not re.search(r'/', va): + if re.search(r':', va): + va = "{0}/128".format(va) + else: + va = "{0}/32".format(va) + config.set(group_path + ["virtual-address"], value=va, replace=False) + + # Sync group + if "sync_group" in g: + config.set(sync_path + [g["sync_group"], "member"], value=group_name, replace=False) + + # Set the tag flag + config.set_tag(base_group_path) + if config.exists(sync_path): + config.set_tag(sync_path) diff --git a/src/migration-scripts/vrrp/2-to-3 b/src/migration-scripts/vrrp/2-to-3 old mode 100755 new mode 100644 index ed583b489..468918f91 --- a/src/migration-scripts/vrrp/2-to-3 +++ b/src/migration-scripts/vrrp/2-to-3 @@ -1,62 +1,44 @@ -#!/usr/bin/env python3 +# Copyright 2021-2024 VyOS maintainers and contributors <maintainers@vyos.io> # -# Copyright (C) 2021 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 program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, +# 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 General Public License for more details. +# 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 General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. # T3847: vrrp config cleanup -from sys import argv from vyos.configtree import ConfigTree -if len(argv) < 2: - print('Must specify file name!') - exit(1) - -file_name = argv[1] - -with open(file_name, 'r') as f: - config_file = f.read() - base = ['high-availability', 'vrrp'] -config = ConfigTree(config_file) - -if not config.exists(base): - # Nothing to do - exit(0) - -if config.exists(base + ['group']): - for group in config.list_nodes(base + ['group']): - group_base = base + ['group', group] - - # Deprecated option - tmp = group_base + ['transition-script', 'mode-force'] - if config.exists(tmp): - config.delete(tmp) - - # Rename virtual-address -> address - tmp = group_base + ['virtual-address'] - if config.exists(tmp): - config.rename(tmp, 'address') - - # Rename virtual-address-excluded -> excluded-address - tmp = group_base + ['virtual-address-excluded'] - if config.exists(tmp): - config.rename(tmp, 'excluded-address') - -try: - with open(file_name, 'w') as f: - f.write(config.to_string()) -except OSError as e: - print(f'Failed to save the modified config: {e}') - exit(1) + +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + # Nothing to do + return + + if config.exists(base + ['group']): + for group in config.list_nodes(base + ['group']): + group_base = base + ['group', group] + + # Deprecated option + tmp = group_base + ['transition-script', 'mode-force'] + if config.exists(tmp): + config.delete(tmp) + + # Rename virtual-address -> address + tmp = group_base + ['virtual-address'] + if config.exists(tmp): + config.rename(tmp, 'address') + + # Rename virtual-address-excluded -> excluded-address + tmp = group_base + ['virtual-address-excluded'] + if config.exists(tmp): + config.rename(tmp, 'excluded-address') diff --git a/src/migration-scripts/vrrp/3-to-4 b/src/migration-scripts/vrrp/3-to-4 old mode 100755 new mode 100644 index e5d93578c..9f05cf7a1 --- a/src/migration-scripts/vrrp/3-to-4 +++ b/src/migration-scripts/vrrp/3-to-4 @@ -1,51 +1,32 @@ -#!/usr/bin/env python3 +# Copyright 2023-2024 VyOS maintainers and contributors <maintainers@vyos.io> # -# Copyright (C) 2023 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 program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, +# 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 General Public License for more details. +# 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 General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. -from sys import argv from vyos.configtree import ConfigTree -if len(argv) < 2: - print('Must specify file name!') - exit(1) - -file_name = argv[1] - -with open(file_name, 'r') as f: - config_file = f.read() - base = ['high-availability', 'virtual-server'] -config = ConfigTree(config_file) - -if not config.exists(base): - # Nothing to do - exit(0) - -if config.exists(base): - for vs in config.list_nodes(base): - vs_base = base + [vs] - # If the fwmark is used, the address is not required - if not config.exists(vs_base + ['fwmark']): - # add option: 'virtual-server <tag> address x.x.x.x' - config.set(vs_base + ['address'], value=vs) +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + # Nothing to do + return + if config.exists(base): + for vs in config.list_nodes(base): + vs_base = base + [vs] -try: - with open(file_name, 'w') as f: - f.write(config.to_string()) -except OSError as e: - print(f'Failed to save the modified config: {e}') - exit(1) + # If the fwmark is used, the address is not required + if not config.exists(vs_base + ['fwmark']): + # add option: 'virtual-server <tag> address x.x.x.x' + config.set(vs_base + ['address'], value=vs) diff --git a/src/migration-scripts/webproxy/1-to-2 b/src/migration-scripts/webproxy/1-to-2 old mode 100755 new mode 100644 index 03f357878..5a4847474 --- a/src/migration-scripts/webproxy/1-to-2 +++ b/src/migration-scripts/webproxy/1-to-2 @@ -1,39 +1,33 @@ -#!/usr/bin/env python3 +# Copyright 2018-2024 VyOS maintainers and contributors <maintainers@vyos.io> +# +# 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 <http://www.gnu.org/licenses/>. # migrate old style `webproxy proxy-bypass 1.2.3.4/24` # to new style `webproxy whitelist destination-address 1.2.3.4/24` -import sys - from vyos.configtree import ConfigTree -if len(sys.argv) < 2: - print("Must specify file name!") - sys.exit(1) - -file_name = sys.argv[1] - -with open(file_name, 'r') as f: - config_file = f.read() +cfg_webproxy_base = ['service', 'webproxy'] -config = ConfigTree(config_file) +def migrate(config: ConfigTree) -> None: + if not config.exists(cfg_webproxy_base + ['proxy-bypass']): + # Nothing to do + return -cfg_webproxy_base = ['service', 'webproxy'] -if not config.exists(cfg_webproxy_base + ['proxy-bypass']): - # Nothing to do - sys.exit(0) -else: bypass_addresses = config.return_values(cfg_webproxy_base + ['proxy-bypass']) # delete old configuration node config.delete(cfg_webproxy_base + ['proxy-bypass']) for bypass_address in bypass_addresses: # add data to new configuration node config.set(cfg_webproxy_base + ['whitelist', 'destination-address'], value=bypass_address, replace=False) - - # save updated configuration - try: - with open(file_name, 'w') as f: - f.write(config.to_string()) - except OSError as e: - print("Failed to save the modified config: {}".format(e)) - sys.exit(1)