Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions python/vyos/config_mgmt.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
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'
Expand All @@ -56,7 +57,6 @@
'commit_archive': '02vyos-commit-archive',
}

DEFAULT_TIME_MINUTES = 10
timer_name = 'commit-confirm'

config_file = os.path.join(directories['config'], 'config.boot')
Expand Down Expand Up @@ -183,7 +183,7 @@ def __init__(self, session_env=None, config=None):
# 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.
Expand Down Expand Up @@ -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(
Expand Down
21 changes: 20 additions & 1 deletion python/vyos/configsession.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,16 @@
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']
Expand Down Expand Up @@ -300,6 +303,22 @@ def commit(self):

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])
Expand Down Expand Up @@ -344,7 +363,7 @@ def merge_config(self, file_path):
if self._vyconf_session is None:
out = self.__run_command(MERGE_CONFIG + [file_path])
else:
out, _ = 'unimplemented'
out = 'unimplemented'

return out

Expand Down
2 changes: 2 additions & 0 deletions python/vyos/defaults.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,3 +82,5 @@
rt_global_table = rt_symbolic_names['main']

vyconfd_conf = '/etc/vyos/vyconfd.conf'

DEFAULT_COMMIT_CONFIRM_MINUTES = 10
10 changes: 9 additions & 1 deletion src/services/api/rest/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@

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
Expand Down Expand Up @@ -71,6 +72,8 @@ class BaseConfigureModel(BasePathModel):


class ConfigureModel(ApiModel, BaseConfigureModel):
confirm_time: StrictInt = 0

class Config:
json_schema_extra = {
'example': {
Expand All @@ -81,8 +84,12 @@ class Config:
}


class ConfirmModel(ApiModel):
op: StrictStr

class ConfigureListModel(ApiModel):
commands: List[BaseConfigureModel]
confirm_time: StrictInt = 0

class Config:
json_schema_extra = {
Expand Down Expand Up @@ -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'
}
Expand Down
76 changes: 67 additions & 9 deletions src/services/api/rest/routers.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
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
Expand Down Expand Up @@ -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,
Expand All @@ -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]
Expand All @@ -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:
Expand Down Expand Up @@ -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:
Expand All @@ -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:
Expand All @@ -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,
):
Expand Down Expand Up @@ -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:
Expand All @@ -527,18 +569,34 @@ 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:
return error(400, str(e))
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)

Expand Down
Loading