diff options
author | John Estabrook <jestabro@vyos.io> | 2025-07-08 10:11:57 -0500 |
---|---|---|
committer | GitHub <noreply@github.com> | 2025-07-08 10:11:57 -0500 |
commit | 4b34c65d4129eede784d5747653bd81f8d40f299 (patch) | |
tree | 012976de08a49c6640c958b9b9c2fc6d7b836445 /python | |
parent | 2fa5d19d86c82f4519bd6aa05ba0c7d571172f3a (diff) | |
parent | 4d7a3a972a11ead1386ae2719b70dbfc2411831f (diff) | |
download | vyos-1x-4b34c65d4129eede784d5747653bd81f8d40f299.tar.gz vyos-1x-4b34c65d4129eede784d5747653bd81f8d40f299.zip |
Merge pull request #4574 from jestabro/merge-config
T7499: update config merge tools
Diffstat (limited to 'python')
-rw-r--r-- | python/vyos/config.py | 2 | ||||
-rw-r--r-- | python/vyos/configsession.py | 18 | ||||
-rw-r--r-- | python/vyos/configsource.py | 5 | ||||
-rw-r--r-- | python/vyos/configtree.py | 24 | ||||
-rw-r--r-- | python/vyos/proto/vyconf_pb2.py | 70 | ||||
-rw-r--r-- | python/vyos/proto/vyconf_proto.py | 10 | ||||
-rw-r--r-- | python/vyos/remote.py | 44 | ||||
-rw-r--r-- | python/vyos/vyconf_session.py | 61 |
8 files changed, 175 insertions, 59 deletions
diff --git a/python/vyos/config.py b/python/vyos/config.py index 6ba8834cb..6f7c76ca7 100644 --- a/python/vyos/config.py +++ b/python/vyos/config.py @@ -134,9 +134,11 @@ class Config(object): subtrees. """ def __init__(self, session_env=None, config_source=None): + self.vyconf_session = None if config_source is None: if vyconf_backend() and boot_configuration_complete(): self._config_source = ConfigSourceVyconfSession(session_env) + self.vyconf_session = self._config_source._vyconf_session else: self._config_source = ConfigSourceSession(session_env) else: diff --git a/python/vyos/configsession.py b/python/vyos/configsession.py index 216069992..50f93f890 100644 --- a/python/vyos/configsession.py +++ b/python/vyos/configsession.py @@ -182,8 +182,9 @@ class ConfigSession(object): self.__run_command([CLI_SHELL_API, 'setupSession']) if vyconf_backend() and boot_configuration_complete(): - self._vyconf_session = VyconfSession(pid=session_id, - on_error=ConfigSessionError) + self._vyconf_session = VyconfSession( + pid=session_id, on_error=ConfigSessionError + ) else: self._vyconf_session = None @@ -342,7 +343,7 @@ class ConfigSession(object): if self._vyconf_session is None: out = self.__run_command(LOAD_CONFIG + [file_path]) else: - out, _ = self._vyconf_session.load_config(file=file_path) + out, _ = self._vyconf_session.load_config(file_name=file_path) return out @@ -359,15 +360,18 @@ class ConfigSession(object): if self._vyconf_session is None: out = self.__run_command(MIGRATE_LOAD_CONFIG + [file_path]) else: - out, _ = self._vyconf_session.load_config(file=file_path, migrate=True) + out, _ = self._vyconf_session.load_config(file_name=file_path, migrate=True) return out - def merge_config(self, file_path): + def merge_config(self, file_path, destructive=False): if self._vyconf_session is None: - out = self.__run_command(MERGE_CONFIG + [file_path]) + destr = ['--destructive'] if destructive else [] + out = self.__run_command(MERGE_CONFIG + [file_path] + destr) else: - out = 'unimplemented' + out, _ = self._vyconf_session.merge_config( + file_name=file_path, destructive=destructive + ) return out diff --git a/python/vyos/configsource.py b/python/vyos/configsource.py index 3931f1295..949216722 100644 --- a/python/vyos/configsource.py +++ b/python/vyos/configsource.py @@ -345,6 +345,11 @@ class ConfigSourceVyconfSession(ConfigSource): self._running_config = ConfigTree(internal=self.running_cache_path) self._session_config = ConfigTree(internal=self.session_cache_path) + if os.path.isfile(self.running_cache_path): + os.remove(self.running_cache_path) + if os.path.isfile(self.session_cache_path): + os.remove(self.session_cache_path) + # N.B. level not yet implemented pending integration with legacy CLI # cf. T7374 self._level = [] diff --git a/python/vyos/configtree.py b/python/vyos/configtree.py index 9b3755841..ba3f1e368 100644 --- a/python/vyos/configtree.py +++ b/python/vyos/configtree.py @@ -233,7 +233,7 @@ class ConfigTree(object): return self.__version def write_cache(self, file_name): - self.__write_internal(self._get_config(), file_name) + self.__write_internal(self._get_config(), file_name.encode()) def to_string(self, ordered_values=False, no_version=False): config_string = self.__to_string(self.__config, ordered_values).decode() @@ -499,6 +499,28 @@ def union(left, right, libpath=LIBPATH): return tree +def merge(left, right, destructive=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') + + __lib = cdll.LoadLibrary(libpath) + __tree_merge = __lib.tree_merge + __tree_merge.argtypes = [c_bool, c_void_p, c_void_p] + __tree_merge.restype = c_void_p + __get_error = __lib.get_error + __get_error.argtypes = [] + __get_error.restype = c_char_p + + res = __tree_merge(destructive, 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') diff --git a/python/vyos/proto/vyconf_pb2.py b/python/vyos/proto/vyconf_pb2.py index 3d5042888..4bf0eb2e0 100644 --- a/python/vyos/proto/vyconf_pb2.py +++ b/python/vyos/proto/vyconf_pb2.py @@ -13,17 +13,17 @@ _sym_db = _symbol_database.Default() -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x0cvyconf.proto\"\x89\x15\n\x07Request\x12!\n\x06prompt\x18\x01 \x01(\x0b\x32\x0f.Request.PromptH\x00\x12.\n\rsetup_session\x18\x02 \x01(\x0b\x32\x15.Request.SetupSessionH\x00\x12\x1b\n\x03set\x18\x03 \x01(\x0b\x32\x0c.Request.SetH\x00\x12!\n\x06\x64\x65lete\x18\x04 \x01(\x0b\x32\x0f.Request.DeleteH\x00\x12!\n\x06rename\x18\x05 \x01(\x0b\x32\x0f.Request.RenameH\x00\x12\x1d\n\x04\x63opy\x18\x06 \x01(\x0b\x32\r.Request.CopyH\x00\x12#\n\x07\x63omment\x18\x07 \x01(\x0b\x32\x10.Request.CommentH\x00\x12!\n\x06\x63ommit\x18\x08 \x01(\x0b\x32\x0f.Request.CommitH\x00\x12%\n\x08rollback\x18\t \x01(\x0b\x32\x11.Request.RollbackH\x00\x12\x1f\n\x05merge\x18\n \x01(\x0b\x32\x0e.Request.MergeH\x00\x12\x1d\n\x04save\x18\x0b \x01(\x0b\x32\r.Request.SaveH\x00\x12*\n\x0bshow_config\x18\x0c \x01(\x0b\x32\x13.Request.ShowConfigH\x00\x12!\n\x06\x65xists\x18\r \x01(\x0b\x32\x0f.Request.ExistsH\x00\x12&\n\tget_value\x18\x0e \x01(\x0b\x32\x11.Request.GetValueH\x00\x12(\n\nget_values\x18\x0f \x01(\x0b\x32\x12.Request.GetValuesH\x00\x12.\n\rlist_children\x18\x10 \x01(\x0b\x32\x15.Request.ListChildrenH\x00\x12)\n\x0brun_op_mode\x18\x11 \x01(\x0b\x32\x12.Request.RunOpModeH\x00\x12#\n\x07\x63onfirm\x18\x12 \x01(\x0b\x32\x10.Request.ConfirmH\x00\x12\x43\n\x18\x65nter_configuration_mode\x18\x13 \x01(\x0b\x32\x1f.Request.EnterConfigurationModeH\x00\x12\x41\n\x17\x65xit_configuration_mode\x18\x14 \x01(\x0b\x32\x1e.Request.ExitConfigurationModeH\x00\x12%\n\x08validate\x18\x15 \x01(\x0b\x32\x11.Request.ValidateH\x00\x12%\n\x08teardown\x18\x16 \x01(\x0b\x32\x11.Request.TeardownH\x00\x12\x30\n\x0ereload_reftree\x18\x17 \x01(\x0b\x32\x16.Request.ReloadReftreeH\x00\x12\x1d\n\x04load\x18\x18 \x01(\x0b\x32\r.Request.LoadH\x00\x12#\n\x07\x64iscard\x18\x19 \x01(\x0b\x32\x10.Request.DiscardH\x00\x12\x32\n\x0fsession_changed\x18\x1a \x01(\x0b\x32\x17.Request.SessionChangedH\x00\x12/\n\x0esession_of_pid\x18\x1b \x01(\x0b\x32\x15.Request.SessionOfPidH\x00\x12\x37\n\x12session_update_pid\x18\x1c \x01(\x0b\x32\x19.Request.SessionUpdatePidH\x00\x12(\n\nget_config\x18\x1d \x01(\x0b\x32\x12.Request.GetConfigH\x00\x1a\x08\n\x06Prompt\x1aP\n\x0cSetupSession\x12\x11\n\tClientPid\x18\x01 \x02(\x05\x12\x19\n\x11\x43lientApplication\x18\x02 \x01(\t\x12\x12\n\nOnBehalfOf\x18\x03 \x01(\x05\x1a!\n\x0cSessionOfPid\x12\x11\n\tClientPid\x18\x01 \x02(\x05\x1a%\n\x10SessionUpdatePid\x12\x11\n\tClientPid\x18\x01 \x02(\x05\x1a\x1a\n\tGetConfig\x12\r\n\x05\x64ummy\x18\x01 \x01(\x05\x1a\x1e\n\x08Teardown\x12\x12\n\nOnBehalfOf\x18\x01 \x01(\x05\x1a\x46\n\x08Validate\x12\x0c\n\x04Path\x18\x01 \x03(\t\x12,\n\routput_format\x18\x02 \x01(\x0e\x32\x15.Request.OutputFormat\x1a\x13\n\x03Set\x12\x0c\n\x04Path\x18\x01 \x03(\t\x1a\x16\n\x06\x44\x65lete\x12\x0c\n\x04Path\x18\x01 \x03(\t\x1a\x18\n\x07\x44iscard\x12\r\n\x05\x64ummy\x18\x01 \x01(\x05\x1a\x1f\n\x0eSessionChanged\x12\r\n\x05\x64ummy\x18\x01 \x01(\x05\x1a\x35\n\x06Rename\x12\x11\n\tEditLevel\x18\x01 \x03(\t\x12\x0c\n\x04\x46rom\x18\x02 \x02(\t\x12\n\n\x02To\x18\x03 \x02(\t\x1a\x33\n\x04\x43opy\x12\x11\n\tEditLevel\x18\x01 \x03(\t\x12\x0c\n\x04\x46rom\x18\x02 \x02(\t\x12\n\n\x02To\x18\x03 \x02(\t\x1a(\n\x07\x43omment\x12\x0c\n\x04Path\x18\x01 \x03(\t\x12\x0f\n\x07\x43omment\x18\x02 \x02(\t\x1aR\n\x06\x43ommit\x12\x0f\n\x07\x43onfirm\x18\x01 \x01(\x08\x12\x16\n\x0e\x43onfirmTimeout\x18\x02 \x01(\x05\x12\x0f\n\x07\x43omment\x18\x03 \x01(\t\x12\x0e\n\x06\x44ryRun\x18\x04 \x01(\x08\x1a\x1c\n\x08Rollback\x12\x10\n\x08Revision\x18\x01 \x02(\x05\x1a?\n\x04Load\x12\x10\n\x08Location\x18\x01 \x02(\t\x12%\n\x06\x66ormat\x18\x02 \x01(\x0e\x32\x15.Request.ConfigFormat\x1a@\n\x05Merge\x12\x10\n\x08Location\x18\x01 \x02(\t\x12%\n\x06\x66ormat\x18\x02 \x01(\x0e\x32\x15.Request.ConfigFormat\x1a?\n\x04Save\x12\x10\n\x08Location\x18\x01 \x02(\t\x12%\n\x06\x66ormat\x18\x02 \x01(\x0e\x32\x15.Request.ConfigFormat\x1a\x41\n\nShowConfig\x12\x0c\n\x04Path\x18\x01 \x03(\t\x12%\n\x06\x66ormat\x18\x02 \x01(\x0e\x32\x15.Request.ConfigFormat\x1a\x16\n\x06\x45xists\x12\x0c\n\x04Path\x18\x01 \x03(\t\x1a\x46\n\x08GetValue\x12\x0c\n\x04Path\x18\x01 \x03(\t\x12,\n\routput_format\x18\x02 \x01(\x0e\x32\x15.Request.OutputFormat\x1aG\n\tGetValues\x12\x0c\n\x04Path\x18\x01 \x03(\t\x12,\n\routput_format\x18\x02 \x01(\x0e\x32\x15.Request.OutputFormat\x1aJ\n\x0cListChildren\x12\x0c\n\x04Path\x18\x01 \x03(\t\x12,\n\routput_format\x18\x02 \x01(\x0e\x32\x15.Request.OutputFormat\x1aG\n\tRunOpMode\x12\x0c\n\x04Path\x18\x01 \x03(\t\x12,\n\routput_format\x18\x02 \x01(\x0e\x32\x15.Request.OutputFormat\x1a\t\n\x07\x43onfirm\x1a\x46\n\x16\x45nterConfigurationMode\x12\x11\n\tExclusive\x18\x01 \x02(\x08\x12\x19\n\x11OverrideExclusive\x18\x02 \x02(\x08\x1a\x17\n\x15\x45xitConfigurationMode\x1a#\n\rReloadReftree\x12\x12\n\nOnBehalfOf\x18\x01 \x01(\x05\"#\n\x0c\x43onfigFormat\x12\t\n\x05\x43URLY\x10\x00\x12\x08\n\x04JSON\x10\x01\")\n\x0cOutputFormat\x12\x0c\n\x08OutPlain\x10\x00\x12\x0b\n\x07OutJSON\x10\x01\x42\x05\n\x03msg\";\n\x0fRequestEnvelope\x12\r\n\x05token\x18\x01 \x01(\t\x12\x19\n\x07request\x18\x02 \x02(\x0b\x32\x08.Request\"S\n\x08Response\x12\x17\n\x06status\x18\x01 \x02(\x0e\x32\x07.Errnum\x12\x0e\n\x06output\x18\x02 \x01(\t\x12\r\n\x05\x65rror\x18\x03 \x01(\t\x12\x0f\n\x07warning\x18\x04 \x01(\t*\xd2\x01\n\x06\x45rrnum\x12\x0b\n\x07SUCCESS\x10\x00\x12\x08\n\x04\x46\x41IL\x10\x01\x12\x10\n\x0cINVALID_PATH\x10\x02\x12\x11\n\rINVALID_VALUE\x10\x03\x12\x16\n\x12\x43OMMIT_IN_PROGRESS\x10\x04\x12\x18\n\x14\x43ONFIGURATION_LOCKED\x10\x05\x12\x12\n\x0eINTERNAL_ERROR\x10\x06\x12\x15\n\x11PERMISSION_DENIED\x10\x07\x12\x17\n\x13PATH_ALREADY_EXISTS\x10\x08\x12\x16\n\x12UNCOMMITED_CHANGES\x10\t') +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x0cvyconf.proto\"\xae\x15\n\x07Request\x12!\n\x06prompt\x18\x01 \x01(\x0b\x32\x0f.Request.PromptH\x00\x12.\n\rsetup_session\x18\x02 \x01(\x0b\x32\x15.Request.SetupSessionH\x00\x12\x1b\n\x03set\x18\x03 \x01(\x0b\x32\x0c.Request.SetH\x00\x12!\n\x06\x64\x65lete\x18\x04 \x01(\x0b\x32\x0f.Request.DeleteH\x00\x12!\n\x06rename\x18\x05 \x01(\x0b\x32\x0f.Request.RenameH\x00\x12\x1d\n\x04\x63opy\x18\x06 \x01(\x0b\x32\r.Request.CopyH\x00\x12#\n\x07\x63omment\x18\x07 \x01(\x0b\x32\x10.Request.CommentH\x00\x12!\n\x06\x63ommit\x18\x08 \x01(\x0b\x32\x0f.Request.CommitH\x00\x12%\n\x08rollback\x18\t \x01(\x0b\x32\x11.Request.RollbackH\x00\x12\x1f\n\x05merge\x18\n \x01(\x0b\x32\x0e.Request.MergeH\x00\x12\x1d\n\x04save\x18\x0b \x01(\x0b\x32\r.Request.SaveH\x00\x12*\n\x0bshow_config\x18\x0c \x01(\x0b\x32\x13.Request.ShowConfigH\x00\x12!\n\x06\x65xists\x18\r \x01(\x0b\x32\x0f.Request.ExistsH\x00\x12&\n\tget_value\x18\x0e \x01(\x0b\x32\x11.Request.GetValueH\x00\x12(\n\nget_values\x18\x0f \x01(\x0b\x32\x12.Request.GetValuesH\x00\x12.\n\rlist_children\x18\x10 \x01(\x0b\x32\x15.Request.ListChildrenH\x00\x12)\n\x0brun_op_mode\x18\x11 \x01(\x0b\x32\x12.Request.RunOpModeH\x00\x12#\n\x07\x63onfirm\x18\x12 \x01(\x0b\x32\x10.Request.ConfirmH\x00\x12\x43\n\x18\x65nter_configuration_mode\x18\x13 \x01(\x0b\x32\x1f.Request.EnterConfigurationModeH\x00\x12\x41\n\x17\x65xit_configuration_mode\x18\x14 \x01(\x0b\x32\x1e.Request.ExitConfigurationModeH\x00\x12%\n\x08validate\x18\x15 \x01(\x0b\x32\x11.Request.ValidateH\x00\x12%\n\x08teardown\x18\x16 \x01(\x0b\x32\x11.Request.TeardownH\x00\x12\x30\n\x0ereload_reftree\x18\x17 \x01(\x0b\x32\x16.Request.ReloadReftreeH\x00\x12\x1d\n\x04load\x18\x18 \x01(\x0b\x32\r.Request.LoadH\x00\x12#\n\x07\x64iscard\x18\x19 \x01(\x0b\x32\x10.Request.DiscardH\x00\x12\x32\n\x0fsession_changed\x18\x1a \x01(\x0b\x32\x17.Request.SessionChangedH\x00\x12/\n\x0esession_of_pid\x18\x1b \x01(\x0b\x32\x15.Request.SessionOfPidH\x00\x12\x37\n\x12session_update_pid\x18\x1c \x01(\x0b\x32\x19.Request.SessionUpdatePidH\x00\x12(\n\nget_config\x18\x1d \x01(\x0b\x32\x12.Request.GetConfigH\x00\x1a\x08\n\x06Prompt\x1aP\n\x0cSetupSession\x12\x11\n\tClientPid\x18\x01 \x02(\x05\x12\x19\n\x11\x43lientApplication\x18\x02 \x01(\t\x12\x12\n\nOnBehalfOf\x18\x03 \x01(\x05\x1a!\n\x0cSessionOfPid\x12\x11\n\tClientPid\x18\x01 \x02(\x05\x1a%\n\x10SessionUpdatePid\x12\x11\n\tClientPid\x18\x01 \x02(\x05\x1a\x1a\n\tGetConfig\x12\r\n\x05\x64ummy\x18\x01 \x01(\x05\x1a\x1e\n\x08Teardown\x12\x12\n\nOnBehalfOf\x18\x01 \x01(\x05\x1a\x46\n\x08Validate\x12\x0c\n\x04Path\x18\x01 \x03(\t\x12,\n\routput_format\x18\x02 \x01(\x0e\x32\x15.Request.OutputFormat\x1a\x13\n\x03Set\x12\x0c\n\x04Path\x18\x01 \x03(\t\x1a\x16\n\x06\x44\x65lete\x12\x0c\n\x04Path\x18\x01 \x03(\t\x1a\x18\n\x07\x44iscard\x12\r\n\x05\x64ummy\x18\x01 \x01(\x05\x1a\x1f\n\x0eSessionChanged\x12\r\n\x05\x64ummy\x18\x01 \x01(\x05\x1a\x35\n\x06Rename\x12\x11\n\tEditLevel\x18\x01 \x03(\t\x12\x0c\n\x04\x46rom\x18\x02 \x02(\t\x12\n\n\x02To\x18\x03 \x02(\t\x1a\x33\n\x04\x43opy\x12\x11\n\tEditLevel\x18\x01 \x03(\t\x12\x0c\n\x04\x46rom\x18\x02 \x02(\t\x12\n\n\x02To\x18\x03 \x02(\t\x1a(\n\x07\x43omment\x12\x0c\n\x04Path\x18\x01 \x03(\t\x12\x0f\n\x07\x43omment\x18\x02 \x02(\t\x1aR\n\x06\x43ommit\x12\x0f\n\x07\x43onfirm\x18\x01 \x01(\x08\x12\x16\n\x0e\x43onfirmTimeout\x18\x02 \x01(\x05\x12\x0f\n\x07\x43omment\x18\x03 \x01(\t\x12\x0e\n\x06\x44ryRun\x18\x04 \x01(\x08\x1a\x1c\n\x08Rollback\x12\x10\n\x08Revision\x18\x01 \x02(\x05\x1aO\n\x04Load\x12\x10\n\x08Location\x18\x01 \x02(\t\x12\x0e\n\x06\x63\x61\x63hed\x18\x02 \x02(\x08\x12%\n\x06\x66ormat\x18\x03 \x01(\x0e\x32\x15.Request.ConfigFormat\x1aU\n\x05Merge\x12\x10\n\x08Location\x18\x01 \x02(\t\x12\x13\n\x0b\x64\x65structive\x18\x02 \x02(\x08\x12%\n\x06\x66ormat\x18\x03 \x01(\x0e\x32\x15.Request.ConfigFormat\x1a?\n\x04Save\x12\x10\n\x08Location\x18\x01 \x02(\t\x12%\n\x06\x66ormat\x18\x02 \x01(\x0e\x32\x15.Request.ConfigFormat\x1a\x41\n\nShowConfig\x12\x0c\n\x04Path\x18\x01 \x03(\t\x12%\n\x06\x66ormat\x18\x02 \x01(\x0e\x32\x15.Request.ConfigFormat\x1a\x16\n\x06\x45xists\x12\x0c\n\x04Path\x18\x01 \x03(\t\x1a\x46\n\x08GetValue\x12\x0c\n\x04Path\x18\x01 \x03(\t\x12,\n\routput_format\x18\x02 \x01(\x0e\x32\x15.Request.OutputFormat\x1aG\n\tGetValues\x12\x0c\n\x04Path\x18\x01 \x03(\t\x12,\n\routput_format\x18\x02 \x01(\x0e\x32\x15.Request.OutputFormat\x1aJ\n\x0cListChildren\x12\x0c\n\x04Path\x18\x01 \x03(\t\x12,\n\routput_format\x18\x02 \x01(\x0e\x32\x15.Request.OutputFormat\x1aG\n\tRunOpMode\x12\x0c\n\x04Path\x18\x01 \x03(\t\x12,\n\routput_format\x18\x02 \x01(\x0e\x32\x15.Request.OutputFormat\x1a\t\n\x07\x43onfirm\x1a\x46\n\x16\x45nterConfigurationMode\x12\x11\n\tExclusive\x18\x01 \x02(\x08\x12\x19\n\x11OverrideExclusive\x18\x02 \x02(\x08\x1a\x17\n\x15\x45xitConfigurationMode\x1a#\n\rReloadReftree\x12\x12\n\nOnBehalfOf\x18\x01 \x01(\x05\"#\n\x0c\x43onfigFormat\x12\t\n\x05\x43URLY\x10\x00\x12\x08\n\x04JSON\x10\x01\")\n\x0cOutputFormat\x12\x0c\n\x08OutPlain\x10\x00\x12\x0b\n\x07OutJSON\x10\x01\x42\x05\n\x03msg\";\n\x0fRequestEnvelope\x12\r\n\x05token\x18\x01 \x01(\t\x12\x19\n\x07request\x18\x02 \x02(\x0b\x32\x08.Request\"S\n\x08Response\x12\x17\n\x06status\x18\x01 \x02(\x0e\x32\x07.Errnum\x12\x0e\n\x06output\x18\x02 \x01(\t\x12\r\n\x05\x65rror\x18\x03 \x01(\t\x12\x0f\n\x07warning\x18\x04 \x01(\t*\xd2\x01\n\x06\x45rrnum\x12\x0b\n\x07SUCCESS\x10\x00\x12\x08\n\x04\x46\x41IL\x10\x01\x12\x10\n\x0cINVALID_PATH\x10\x02\x12\x11\n\rINVALID_VALUE\x10\x03\x12\x16\n\x12\x43OMMIT_IN_PROGRESS\x10\x04\x12\x18\n\x14\x43ONFIGURATION_LOCKED\x10\x05\x12\x12\n\x0eINTERNAL_ERROR\x10\x06\x12\x15\n\x11PERMISSION_DENIED\x10\x07\x12\x17\n\x13PATH_ALREADY_EXISTS\x10\x08\x12\x16\n\x12UNCOMMITED_CHANGES\x10\t') _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, globals()) _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'vyconf_pb2', globals()) if _descriptor._USE_C_DESCRIPTORS == False: DESCRIPTOR._options = None - _ERRNUM._serialized_start=2863 - _ERRNUM._serialized_end=3073 + _ERRNUM._serialized_start=2900 + _ERRNUM._serialized_end=3110 _REQUEST._serialized_start=17 - _REQUEST._serialized_end=2714 + _REQUEST._serialized_end=2751 _REQUEST_PROMPT._serialized_start=1237 _REQUEST_PROMPT._serialized_end=1245 _REQUEST_SETUPSESSION._serialized_start=1247 @@ -57,37 +57,37 @@ if _descriptor._USE_C_DESCRIPTORS == False: _REQUEST_ROLLBACK._serialized_start=1873 _REQUEST_ROLLBACK._serialized_end=1901 _REQUEST_LOAD._serialized_start=1903 - _REQUEST_LOAD._serialized_end=1966 - _REQUEST_MERGE._serialized_start=1968 - _REQUEST_MERGE._serialized_end=2032 - _REQUEST_SAVE._serialized_start=2034 - _REQUEST_SAVE._serialized_end=2097 - _REQUEST_SHOWCONFIG._serialized_start=2099 - _REQUEST_SHOWCONFIG._serialized_end=2164 - _REQUEST_EXISTS._serialized_start=2166 - _REQUEST_EXISTS._serialized_end=2188 - _REQUEST_GETVALUE._serialized_start=2190 - _REQUEST_GETVALUE._serialized_end=2260 - _REQUEST_GETVALUES._serialized_start=2262 - _REQUEST_GETVALUES._serialized_end=2333 - _REQUEST_LISTCHILDREN._serialized_start=2335 - _REQUEST_LISTCHILDREN._serialized_end=2409 - _REQUEST_RUNOPMODE._serialized_start=2411 - _REQUEST_RUNOPMODE._serialized_end=2482 + _REQUEST_LOAD._serialized_end=1982 + _REQUEST_MERGE._serialized_start=1984 + _REQUEST_MERGE._serialized_end=2069 + _REQUEST_SAVE._serialized_start=2071 + _REQUEST_SAVE._serialized_end=2134 + _REQUEST_SHOWCONFIG._serialized_start=2136 + _REQUEST_SHOWCONFIG._serialized_end=2201 + _REQUEST_EXISTS._serialized_start=2203 + _REQUEST_EXISTS._serialized_end=2225 + _REQUEST_GETVALUE._serialized_start=2227 + _REQUEST_GETVALUE._serialized_end=2297 + _REQUEST_GETVALUES._serialized_start=2299 + _REQUEST_GETVALUES._serialized_end=2370 + _REQUEST_LISTCHILDREN._serialized_start=2372 + _REQUEST_LISTCHILDREN._serialized_end=2446 + _REQUEST_RUNOPMODE._serialized_start=2448 + _REQUEST_RUNOPMODE._serialized_end=2519 _REQUEST_CONFIRM._serialized_start=1799 _REQUEST_CONFIRM._serialized_end=1808 - _REQUEST_ENTERCONFIGURATIONMODE._serialized_start=2495 - _REQUEST_ENTERCONFIGURATIONMODE._serialized_end=2565 - _REQUEST_EXITCONFIGURATIONMODE._serialized_start=2567 - _REQUEST_EXITCONFIGURATIONMODE._serialized_end=2590 - _REQUEST_RELOADREFTREE._serialized_start=2592 - _REQUEST_RELOADREFTREE._serialized_end=2627 - _REQUEST_CONFIGFORMAT._serialized_start=2629 - _REQUEST_CONFIGFORMAT._serialized_end=2664 - _REQUEST_OUTPUTFORMAT._serialized_start=2666 - _REQUEST_OUTPUTFORMAT._serialized_end=2707 - _REQUESTENVELOPE._serialized_start=2716 - _REQUESTENVELOPE._serialized_end=2775 - _RESPONSE._serialized_start=2777 - _RESPONSE._serialized_end=2860 + _REQUEST_ENTERCONFIGURATIONMODE._serialized_start=2532 + _REQUEST_ENTERCONFIGURATIONMODE._serialized_end=2602 + _REQUEST_EXITCONFIGURATIONMODE._serialized_start=2604 + _REQUEST_EXITCONFIGURATIONMODE._serialized_end=2627 + _REQUEST_RELOADREFTREE._serialized_start=2629 + _REQUEST_RELOADREFTREE._serialized_end=2664 + _REQUEST_CONFIGFORMAT._serialized_start=2666 + _REQUEST_CONFIGFORMAT._serialized_end=2701 + _REQUEST_OUTPUTFORMAT._serialized_start=2703 + _REQUEST_OUTPUTFORMAT._serialized_end=2744 + _REQUESTENVELOPE._serialized_start=2753 + _REQUESTENVELOPE._serialized_end=2812 + _RESPONSE._serialized_start=2814 + _RESPONSE._serialized_end=2897 # @@protoc_insertion_point(module_scope) diff --git a/python/vyos/proto/vyconf_proto.py b/python/vyos/proto/vyconf_proto.py index 404ef2f27..ec62a6e35 100644 --- a/python/vyos/proto/vyconf_proto.py +++ b/python/vyos/proto/vyconf_proto.py @@ -100,11 +100,13 @@ class Rollback: @dataclass class Load: Location: str = "" + cached: bool = False format: ConfigFormat = None @dataclass class Merge: Location: str = "" + destructive: bool = False format: ConfigFormat = None @dataclass @@ -298,14 +300,14 @@ def set_request_rollback(token: str = None, revision: int = 0): req_env = RequestEnvelope(token, req) return req_env -def set_request_load(token: str = None, location: str = "", format: ConfigFormat = None): - reqi = Load (location, format) +def set_request_load(token: str = None, location: str = "", cached: bool = False, format: ConfigFormat = None): + reqi = Load (location, cached, format) req = Request(load=reqi) req_env = RequestEnvelope(token, req) return req_env -def set_request_merge(token: str = None, location: str = "", format: ConfigFormat = None): - reqi = Merge (location, format) +def set_request_merge(token: str = None, location: str = "", destructive: bool = False, format: ConfigFormat = None): + reqi = Merge (location, destructive, format) req = Request(merge=reqi) req_env = RequestEnvelope(token, req) return req_env diff --git a/python/vyos/remote.py b/python/vyos/remote.py index f6ab5c3f9..b73f486c0 100644 --- a/python/vyos/remote.py +++ b/python/vyos/remote.py @@ -22,6 +22,7 @@ import stat import sys import tempfile import urllib.parse +import gzip from contextlib import contextmanager from pathlib import Path @@ -44,6 +45,7 @@ from vyos.utils.misc import begin from vyos.utils.process import cmd, rc_cmd from vyos.version import get_version from vyos.base import Warning +from vyos.defaults import directories CHUNK_SIZE = 8192 @@ -478,3 +480,45 @@ def get_remote_config(urlstring, source_host='', source_port=0): return f.read() finally: os.remove(temp) + + +def get_config_file(file_in: str, file_out: str, source_host='', source_port=0): + protocols = ['scp', 'sftp', 'http', 'https', 'ftp', 'tftp'] + config_dir = directories['config'] + + with tempfile.NamedTemporaryFile() as tmp_file: + if any(file_in.startswith(f'{x}://') for x in protocols): + try: + download( + tmp_file.name, + file_in, + check_space=True, + source_host='', + source_port=0, + raise_error=True, + ) + except Exception as e: + return e + file_name = tmp_file.name + else: + full_path = os.path.realpath(file_in) + if os.path.isfile(full_path): + file_in = full_path + else: + file_in = os.path.join(config_dir, file_in) + if not os.path.isfile(file_in): + return ValueError(f'No such file {file_in}') + + file_name = file_in + + if file_in.endswith('.gz'): + try: + with gzip.open(file_name, 'rb') as f_in: + with open(file_out, 'wb') as f_out: + shutil.copyfileobj(f_in, f_out) + except Exception as e: + return e + else: + shutil.copyfile(file_name, file_out) + + return None diff --git a/python/vyos/vyconf_session.py b/python/vyos/vyconf_session.py index 4a2e6e393..3cf5fb4e3 100644 --- a/python/vyos/vyconf_session.py +++ b/python/vyos/vyconf_session.py @@ -17,7 +17,6 @@ import os import tempfile -import shutil from functools import wraps from typing import Type @@ -30,6 +29,7 @@ from vyos.proto.vyconf_proto import Errnum from vyos.utils.commit import acquire_commit_lock_file from vyos.utils.commit import release_commit_lock_file from vyos.utils.commit import call_commit_hooks +from vyos.remote import get_config_file class VyconfSessionError(Exception): @@ -142,6 +142,10 @@ class VyconfSession: @raise_exception @config_mode def commit(self) -> tuple[str, int]: + if not self.session_changed(): + out = 'No changes to commit' + return out, 0 + lock_fd, out = acquire_commit_lock_file() if lock_fd is None: return out, Errnum.COMMIT_IN_PROGRESS @@ -163,24 +167,57 @@ class VyconfSession: @raise_exception @config_mode - def load_config(self, file: str, migrate: bool = False) -> tuple[str, int]: + def load_config( + self, file_name: str, migrate: bool = False, cached: bool = False + ) -> tuple[str, int]: # pylint: disable=consider-using-with + file_path = tempfile.NamedTemporaryFile(delete=False).name + err = get_config_file(file_name, file_path) + if err: + os.remove(file_path) + return str(err), Errnum.INVALID_VALUE + if not cached: + if migrate: + config_migrate = ConfigMigrate(file_path) + try: + config_migrate.run() + except ConfigMigrateError as e: + os.remove(file_path) + return repr(e), 1 + + out = vyconf_client.send_request( + 'load', token=self.__token, location=file_path, cached=cached + ) + + if not cached: + os.remove(file_path) + + return self.output(out), out.status + + @raise_exception + @config_mode + def merge_config( + self, file_name: str, migrate: bool = False, destructive: bool = False + ) -> tuple[str, int]: + # pylint: disable=consider-using-with + file_path = tempfile.NamedTemporaryFile(delete=False).name + err = get_config_file(file_name, file_path) + if err: + os.remove(file_path) + return str(err), Errnum.INVALID_VALUE if migrate: - tmp = tempfile.NamedTemporaryFile() - shutil.copy2(file, tmp.name) - config_migrate = ConfigMigrate(tmp.name) + config_migrate = ConfigMigrate(file_path) try: config_migrate.run() except ConfigMigrateError as e: - tmp.close() + os.remove(file_path) return repr(e), 1 - file = tmp.name - else: - tmp = '' - out = vyconf_client.send_request('load', token=self.__token, location=file) - if tmp: - tmp.close() + out = vyconf_client.send_request( + 'merge', token=self.__token, location=file_path, destructive=destructive + ) + + os.remove(file_path) return self.output(out), out.status |