diff options
Diffstat (limited to 'python/vyos/configsession.py')
-rw-r--r-- | python/vyos/configsession.py | 221 |
1 files changed, 190 insertions, 31 deletions
diff --git a/python/vyos/configsession.py b/python/vyos/configsession.py index 7d51b94e4..50f93f890 100644 --- a/python/vyos/configsession.py +++ b/python/vyos/configsession.py @@ -1,4 +1,4 @@ -# Copyright (C) 2019-2024 VyOS maintainers and contributors +# Copyright 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; @@ -21,36 +21,75 @@ import subprocess from vyos.defaults import directories from vyos.utils.process import is_systemd_service_running from vyos.utils.dict import dict_to_paths +from vyos.utils.boot import boot_configuration_complete +from vyos.utils.backend import vyconf_backend +from vyos.vyconf_session import VyconfSession +from vyos.base import Warning as Warn +from vyos.defaults import DEFAULT_COMMIT_CONFIRM_MINUTES + CLI_SHELL_API = '/bin/cli-shell-api' SET = '/opt/vyatta/sbin/my_set' DELETE = '/opt/vyatta/sbin/my_delete' COMMENT = '/opt/vyatta/sbin/my_comment' COMMIT = '/opt/vyatta/sbin/my_commit' +COMMIT_CONFIRM = ['/usr/bin/config-mgmt', 'commit_confirm', '-y'] +CONFIRM = ['/usr/bin/config-mgmt', 'confirm'] DISCARD = '/opt/vyatta/sbin/my_discard' SHOW_CONFIG = ['/bin/cli-shell-api', 'showConfig'] LOAD_CONFIG = ['/bin/cli-shell-api', 'loadFile'] MIGRATE_LOAD_CONFIG = ['/usr/libexec/vyos/vyos-load-config.py'] +MERGE_CONFIG = ['/usr/libexec/vyos/vyos-merge-config.py'] SAVE_CONFIG = ['/usr/libexec/vyos/vyos-save-config.py'] -INSTALL_IMAGE = ['/usr/libexec/vyos/op_mode/image_installer.py', - '--action', 'add', '--no-prompt', '--image-path'] +INSTALL_IMAGE = [ + '/usr/libexec/vyos/op_mode/image_installer.py', + '--action', + 'add', + '--no-prompt', + '--image-path', +] IMPORT_PKI = ['/opt/vyatta/bin/vyatta-op-cmd-wrapper', 'import'] -IMPORT_PKI_NO_PROMPT = ['/usr/libexec/vyos/op_mode/pki.py', - '--action', 'import', '--no-prompt'] -REMOVE_IMAGE = ['/usr/libexec/vyos/op_mode/image_manager.py', - '--action', 'delete', '--no-prompt', '--image-name'] -SET_DEFAULT_IMAGE = ['/usr/libexec/vyos/op_mode/image_manager.py', - '--action', 'set', '--no-prompt', '--image-name'] +IMPORT_PKI_NO_PROMPT = [ + '/usr/libexec/vyos/op_mode/pki.py', + 'import_pki', + '--no-prompt', +] +REMOVE_IMAGE = [ + '/usr/libexec/vyos/op_mode/image_manager.py', + '--action', + 'delete', + '--no-prompt', + '--image-name', +] +SET_DEFAULT_IMAGE = [ + '/usr/libexec/vyos/op_mode/image_manager.py', + '--action', + 'set', + '--no-prompt', + '--image-name', +] GENERATE = ['/opt/vyatta/bin/vyatta-op-cmd-wrapper', 'generate'] SHOW = ['/opt/vyatta/bin/vyatta-op-cmd-wrapper', 'show'] RESET = ['/opt/vyatta/bin/vyatta-op-cmd-wrapper', 'reset'] REBOOT = ['/opt/vyatta/bin/vyatta-op-cmd-wrapper', 'reboot'] +RENEW = ['/opt/vyatta/bin/vyatta-op-cmd-wrapper', 'renew'] POWEROFF = ['/opt/vyatta/bin/vyatta-op-cmd-wrapper', 'poweroff'] OP_CMD_ADD = ['/opt/vyatta/bin/vyatta-op-cmd-wrapper', 'add'] OP_CMD_DELETE = ['/opt/vyatta/bin/vyatta-op-cmd-wrapper', 'delete'] +TRACEROUTE = [ + '/usr/libexec/vyos/op_mode/mtr_execute.py', + 'mtr', + '--for-api', + '--report-mode', + '--report-cycles', + '1', + '--json', + '--host', +] # Default "commit via" string -APP = "vyos-http-api" +APP = 'vyos-http-api' + # When started as a service rather than from a user shell, # the process lacks the VyOS-specific environment that comes @@ -61,7 +100,7 @@ def inject_vyos_env(env): env['VYATTA_USER_LEVEL_DIR'] = '/opt/vyatta/etc/shell/level/admin' env['VYATTA_PROCESS_CLIENT'] = 'gui2_rest' env['VYOS_HEADLESS_CLIENT'] = 'vyos_http_api' - env['vyatta_bindir']= '/opt/vyatta/bin' + env['vyatta_bindir'] = '/opt/vyatta/bin' env['vyatta_cfg_templates'] = '/opt/vyatta/share/vyatta-cfg/templates' env['vyatta_configdir'] = directories['vyos_configdir'] env['vyatta_datadir'] = '/opt/vyatta/share' @@ -78,7 +117,7 @@ def inject_vyos_env(env): env['vyos_configdir'] = directories['vyos_configdir'] env['vyos_conf_scripts_dir'] = '/usr/libexec/vyos/conf_mode' env['vyos_datadir'] = '/opt/vyatta/share' - env['vyos_datarootdir']= '/opt/vyatta/share' + env['vyos_datarootdir'] = '/opt/vyatta/share' env['vyos_libdir'] = '/opt/vyatta/lib' env['vyos_libexec_dir'] = '/usr/libexec/vyos' env['vyos_op_scripts_dir'] = '/usr/libexec/vyos/op_mode' @@ -87,6 +126,10 @@ def inject_vyos_env(env): env['vyos_sbin_dir'] = '/usr/sbin' env['vyos_validators_dir'] = '/usr/libexec/vyos/validators' + # with the retirement of the Cstore backend, this will remain as the + # sole indication of legacy CLI config mode, as checked by VyconfSession + env['_OFR_CONFIGURE'] = 'ok' + # if running the vyos-configd daemon, inject the vyshim env var if is_systemd_service_running('vyos-configd.service'): env['vyshim'] = '/usr/sbin/vyshim' @@ -102,7 +145,8 @@ class ConfigSession(object): """ The write API of VyOS. """ - def __init__(self, session_id, app=APP): + + def __init__(self, session_id, app=APP, shared=False): """ Creates a new config session. @@ -116,7 +160,9 @@ class ConfigSession(object): and used the PID for the session identifier. """ - env_str = subprocess.check_output([CLI_SHELL_API, 'getSessionEnv', str(session_id)]) + env_str = subprocess.check_output( + [CLI_SHELL_API, 'getSessionEnv', str(session_id)] + ) self.__session_id = session_id # Extract actual variables from the chunk of shell it outputs @@ -128,21 +174,60 @@ class ConfigSession(object): for k, v in env_list: session_env[k] = v + session_env['CONFIGSESSION_PID'] = str(session_id) + self.__session_env = session_env - self.__session_env["COMMIT_VIA"] = app + self.__session_env['COMMIT_VIA'] = app self.__run_command([CLI_SHELL_API, 'setupSession']) + if vyconf_backend() and boot_configuration_complete(): + self._vyconf_session = VyconfSession( + pid=session_id, on_error=ConfigSessionError + ) + else: + self._vyconf_session = None + + self.shared = shared + def __del__(self): - try: - output = subprocess.check_output([CLI_SHELL_API, 'teardownSession'], env=self.__session_env).decode().strip() - if output: - print("cli-shell-api teardownSession output for sesion {0}: {1}".format(self.__session_id, output), file=sys.stderr) - except Exception as e: - print("Could not tear down session {0}: {1}".format(self.__session_id, e), file=sys.stderr) + if self.shared: + return + if self._vyconf_session is None: + try: + output = ( + subprocess.check_output( + [CLI_SHELL_API, 'teardownSession'], env=self.__session_env + ) + .decode() + .strip() + ) + if output: + print( + 'cli-shell-api teardownSession output for sesion {0}: {1}'.format( + self.__session_id, output + ), + file=sys.stderr, + ) + except Exception as e: + print( + 'Could not tear down session {0}: {1}'.format(self.__session_id, e), + file=sys.stderr, + ) + else: + if self._vyconf_session.session_changed(): + Warn('Exiting with uncommitted changes') + self._vyconf_session.discard() + self._vyconf_session.exit_config_mode() + self._vyconf_session.teardown() def __run_command(self, cmd_list): - p = subprocess.Popen(cmd_list, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, env=self.__session_env) + p = subprocess.Popen( + cmd_list, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + env=self.__session_env, + ) (stdout_data, stderr_data) = p.communicate() output = stdout_data.decode() result = p.wait() @@ -158,7 +243,10 @@ class ConfigSession(object): value = [] else: value = [value] - self.__run_command([SET] + path + value) + if self._vyconf_session is None: + self.__run_command([SET] + path + value) + else: + self._vyconf_session.set(path + value) def set_section(self, path: list, d: dict): try: @@ -172,7 +260,10 @@ class ConfigSession(object): value = [] else: value = [value] - self.__run_command([DELETE] + path + value) + if self._vyconf_session is None: + self.__run_command([DELETE] + path + value) + else: + self._vyconf_session.delete(path + value) def load_section(self, path: list, d: dict): try: @@ -204,34 +295,94 @@ class ConfigSession(object): def comment(self, path, value=None): if not value: - value = [""] + value = [''] else: value = [value] self.__run_command([COMMENT] + path + value) def commit(self): - out = self.__run_command([COMMIT]) + if self._vyconf_session is None: + out = self.__run_command([COMMIT]) + else: + out, _ = self._vyconf_session.commit() + + return out + + def commit_confirm(self, minutes: int = DEFAULT_COMMIT_CONFIRM_MINUTES): + if self._vyconf_session is None: + out = self.__run_command(COMMIT_CONFIRM + [f'-t {minutes}']) + else: + out = 'unimplemented' + + return out + + def confirm(self): + if self._vyconf_session is None: + out = self.__run_command(CONFIRM) + else: + out = 'unimplemented' + return out def discard(self): - self.__run_command([DISCARD]) + if self._vyconf_session is None: + self.__run_command([DISCARD]) + else: + out, _ = self._vyconf_session.discard() def show_config(self, path, format='raw'): - config_data = self.__run_command(SHOW_CONFIG + path) + if self._vyconf_session is None: + config_data = self.__run_command(SHOW_CONFIG + path) + else: + config_data, _ = self._vyconf_session.show_config(path) if format == 'raw': return config_data def load_config(self, file_path): - out = self.__run_command(LOAD_CONFIG + [file_path]) + if self._vyconf_session is None: + out = self.__run_command(LOAD_CONFIG + [file_path]) + else: + out, _ = self._vyconf_session.load_config(file_name=file_path) + return out + def load_explicit(self, file_path): + from vyos.load_config import load + from vyos.load_config import LoadConfigError + + try: + load(file_path, switch='explicit') + except LoadConfigError as e: + raise ConfigSessionError(e) from e + def migrate_and_load_config(self, file_path): - out = self.__run_command(MIGRATE_LOAD_CONFIG + [file_path]) + if self._vyconf_session is None: + out = self.__run_command(MIGRATE_LOAD_CONFIG + [file_path]) + else: + out, _ = self._vyconf_session.load_config(file_name=file_path, migrate=True) + + return out + + def merge_config(self, file_path, destructive=False): + if self._vyconf_session is None: + destr = ['--destructive'] if destructive else [] + out = self.__run_command(MERGE_CONFIG + [file_path] + destr) + else: + out, _ = self._vyconf_session.merge_config( + file_name=file_path, destructive=destructive + ) + return out def save_config(self, file_path): - out = self.__run_command(SAVE_CONFIG + [file_path]) + if self._vyconf_session is None: + out = self.__run_command(SAVE_CONFIG + [file_path]) + else: + out, _ = self._vyconf_session.save_config( + file=file_path, append_version=True + ) + return out def install_image(self, url): @@ -270,6 +421,10 @@ class ConfigSession(object): out = self.__run_command(RESET + path) return out + def renew(self, path): + out = self.__run_command(RENEW + path) + return out + def poweroff(self, path): out = self.__run_command(POWEROFF + path) return out @@ -285,3 +440,7 @@ class ConfigSession(object): def show_container_image(self): out = self.__run_command(SHOW + ['container', 'image']) return out + + def traceroute(self, host): + out = self.__run_command(TRACEROUTE + [host]) + return out |