summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--python/vyos/config_mgmt.py6
-rw-r--r--python/vyos/configsession.py21
-rw-r--r--python/vyos/defaults.py2
-rw-r--r--src/services/api/rest/models.py10
-rw-r--r--src/services/api/rest/routers.py76
5 files changed, 101 insertions, 14 deletions
diff --git a/python/vyos/config_mgmt.py b/python/vyos/config_mgmt.py
index 308eb1cdb..9522dc08f 100644
--- a/python/vyos/config_mgmt.py
+++ b/python/vyos/config_mgmt.py
@@ -44,6 +44,7 @@ from vyos.utils.io import ask_yes_no
from vyos.utils.boot import boot_configuration_complete
from vyos.utils.process import is_systemd_service_active
from vyos.utils.process import rc_cmd
+from vyos.defaults import DEFAULT_COMMIT_CONFIRM_MINUTES
SAVE_CONFIG = '/usr/libexec/vyos/vyos-save-config.py'
config_json = '/run/vyatta/config/config.json'
@@ -56,7 +57,6 @@ commit_hooks = {
'commit_archive': '02vyos-commit-archive',
}
-DEFAULT_TIME_MINUTES = 10
timer_name = 'commit-confirm'
config_file = os.path.join(directories['config'], 'config.boot')
@@ -183,7 +183,7 @@ class ConfigMgmt:
# Console script functions
#
def commit_confirm(
- self, minutes: int = DEFAULT_TIME_MINUTES, no_prompt: bool = False
+ self, minutes: int = DEFAULT_COMMIT_CONFIRM_MINUTES, no_prompt: bool = False
) -> Tuple[str, int]:
"""Commit with reload/reboot to saved config in 'minutes' minutes if
'confirm' call is not issued.
@@ -807,7 +807,7 @@ def run():
'-t',
dest='minutes',
type=int,
- default=DEFAULT_TIME_MINUTES,
+ default=DEFAULT_COMMIT_CONFIRM_MINUTES,
help="Minutes until reboot, unless 'confirm'",
)
commit_confirm.add_argument(
diff --git a/python/vyos/configsession.py b/python/vyos/configsession.py
index 4e0dd23a4..f0d636b89 100644
--- a/python/vyos/configsession.py
+++ b/python/vyos/configsession.py
@@ -25,6 +25,7 @@ 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'
@@ -32,6 +33,8 @@ 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']
@@ -300,6 +303,22 @@ class ConfigSession(object):
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):
if self._vyconf_session is None:
self.__run_command([DISCARD])
@@ -344,7 +363,7 @@ class ConfigSession(object):
if self._vyconf_session is None:
out = self.__run_command(MERGE_CONFIG + [file_path])
else:
- out, _ = 'unimplemented'
+ out = 'unimplemented'
return out
diff --git a/python/vyos/defaults.py b/python/vyos/defaults.py
index e42d92112..b57dcac89 100644
--- a/python/vyos/defaults.py
+++ b/python/vyos/defaults.py
@@ -82,3 +82,5 @@ rt_global_vrf = rt_symbolic_names['main']
rt_global_table = rt_symbolic_names['main']
vyconfd_conf = '/etc/vyos/vyconfd.conf'
+
+DEFAULT_COMMIT_CONFIRM_MINUTES = 10
diff --git a/src/services/api/rest/models.py b/src/services/api/rest/models.py
index 47c7a65b3..c5cb4af48 100644
--- a/src/services/api/rest/models.py
+++ b/src/services/api/rest/models.py
@@ -26,6 +26,7 @@ from typing import Self
from pydantic import BaseModel
from pydantic import StrictStr
+from pydantic import StrictInt
from pydantic import field_validator
from pydantic import model_validator
from fastapi.responses import HTMLResponse
@@ -71,6 +72,8 @@ class BaseConfigureModel(BasePathModel):
class ConfigureModel(ApiModel, BaseConfigureModel):
+ confirm_time: StrictInt = 0
+
class Config:
json_schema_extra = {
'example': {
@@ -81,8 +84,12 @@ class ConfigureModel(ApiModel, BaseConfigureModel):
}
+class ConfirmModel(ApiModel):
+ op: StrictStr
+
class ConfigureListModel(ApiModel):
commands: List[BaseConfigureModel]
+ confirm_time: StrictInt = 0
class Config:
json_schema_extra = {
@@ -135,12 +142,13 @@ class ConfigFileModel(ApiModel):
op: StrictStr
file: StrictStr = None
string: StrictStr = None
+ confirm_time: StrictInt = 0
class Config:
json_schema_extra = {
'example': {
'key': 'id_key',
- 'op': 'save | load | merge',
+ 'op': 'save | load | merge | confirm',
'file': 'filename',
'string': 'config_string'
}
diff --git a/src/services/api/rest/routers.py b/src/services/api/rest/routers.py
index 4866ec5d8..a2e6b4178 100644
--- a/src/services/api/rest/routers.py
+++ b/src/services/api/rest/routers.py
@@ -51,6 +51,7 @@ from .models import error
from .models import responses
from .models import ApiModel
from .models import ConfigureModel
+from .models import ConfirmModel
from .models import ConfigureListModel
from .models import ConfigSectionModel
from .models import ConfigSectionListModel
@@ -302,8 +303,24 @@ def call_commit(s: SessionState):
LOG.warning(f'ConfigSessionError: {e}')
+def call_commit_confirm(s: SessionState):
+ env = s.session.get_session_env()
+ env['IN_COMMIT_CONFIRM'] = 't'
+ try:
+ s.session.commit()
+ except ConfigSessionError as e:
+ s.session.discard()
+ if s.debug:
+ LOG.warning(f'ConfigSessionError:\n {traceback.format_exc()}')
+ else:
+ LOG.warning(f'ConfigSessionError: {e}')
+ finally:
+ del env['IN_COMMIT_CONFIRM']
+
+
def _configure_op(
data: Union[
+ ConfirmModel,
ConfigureModel,
ConfigureListModel,
ConfigSectionModel,
@@ -320,6 +337,11 @@ def _configure_op(
session = state.session
env = session.get_session_env()
+ # A non-zero confirm_time will start commit-confirm timer on commit
+ confirm_time = 0
+ if isinstance(data, (ConfigureModel, ConfigureListModel, ConfigFileModel)):
+ confirm_time = data.confirm_time
+
# Allow users to pass just one command
if not isinstance(data, (ConfigureListModel, ConfigSectionListModel)):
data = [data]
@@ -339,10 +361,16 @@ def _configure_op(
try:
for c in data:
op = c.op
- if not isinstance(c, BaseConfigSectionTreeModel):
+ if not isinstance(c, (ConfirmModel, BaseConfigSectionTreeModel)):
path = c.path
- if isinstance(c, BaseConfigureModel):
+ if isinstance(c, ConfirmModel):
+ if op == 'confirm':
+ msg = session.confirm()
+ else:
+ raise ConfigSessionError(f"'{op}' is not a valid operation")
+
+ elif isinstance(c, BaseConfigureModel):
if c.value:
value = c.value
else:
@@ -388,16 +416,26 @@ def _configure_op(
else:
raise ConfigSessionError(f"'{op}' is not a valid operation")
# end for
+
config = Config(session_env=env)
d = get_config_diff(config)
+ if confirm_time:
+ out = session.commit_confirm(minutes=confirm_time)
+ msg = msg + out if msg else out
+ env['IN_COMMIT_CONFIRM'] = 't'
+
if d.is_node_changed(['service', 'https']):
- background_tasks.add_task(call_commit, state)
- msg = self_ref_msg
+ if confirm_time:
+ background_tasks.add_task(call_commit_confirm, state)
+ else:
+ background_tasks.add_task(call_commit, state)
+ out = self_ref_msg
+ msg = msg + out if msg else out
else:
# capture non-fatal warnings
out = session.commit()
- msg = out if out else msg
+ msg = msg + out if msg else out
LOG.info(f"Configuration modified via HTTP API using key '{state.id}'")
except ConfigSessionError as e:
@@ -414,6 +452,8 @@ def _configure_op(
# Don't give the details away to the outer world
error_msg = 'An internal error occured. Check the logs for details.'
finally:
+ if 'IN_COMMIT_CONFIRM' in env:
+ del env['IN_COMMIT_CONFIRM']
lock.release()
if status != 200:
@@ -433,7 +473,7 @@ def create_path_import_pki_no_prompt(path):
@router.post('/configure')
def configure_op(
- data: Union[ConfigureModel, ConfigureListModel],
+ data: Union[ConfigureModel, ConfigureListModel, ConfirmModel],
request: Request,
background_tasks: BackgroundTasks,
):
@@ -501,6 +541,8 @@ def config_file_op(data: ConfigFileModel, background_tasks: BackgroundTasks):
op = data.op
msg = None
+ lock.acquire()
+
try:
if op == 'save':
if data.file:
@@ -527,11 +569,23 @@ def config_file_op(data: ConfigFileModel, background_tasks: BackgroundTasks):
config = Config(session_env=env)
d = get_config_diff(config)
+ if data.confirm_time:
+ out = session.commit_confirm(minutes=data.confirm_time)
+ msg = msg + out if msg else out
+ env['IN_COMMIT_CONFIRM'] = 't'
+
if d.is_node_changed(['service', 'https']):
- background_tasks.add_task(call_commit, state)
- msg = self_ref_msg
+ if data.confirm_time:
+ background_tasks.add_task(call_commit_confirm, state)
+ else:
+ background_tasks.add_task(call_commit, state)
+ out = self_ref_msg
+ msg = msg + out if msg else out
else:
- session.commit()
+ out = session.commit()
+ msg = msg + out if msg else out
+ elif op == 'confirm':
+ msg = session.confirm()
else:
return error(400, f"'{op}' is not a valid operation")
except ConfigSessionError as e:
@@ -539,6 +593,10 @@ def config_file_op(data: ConfigFileModel, background_tasks: BackgroundTasks):
except Exception:
LOG.critical(traceback.format_exc())
return error(500, 'An internal error occured. Check the logs for details.')
+ finally:
+ if 'IN_COMMIT_CONFIRM' in env:
+ del env['IN_COMMIT_CONFIRM']
+ lock.release()
return success(msg)