From 5f9ea5831391908eeace58c3f496796c69351bd5 Mon Sep 17 00:00:00 2001 From: axolotle Date: Tue, 18 Apr 2023 20:15:25 +0200 Subject: [PATCH] configpanel: update _apply --- src/app.py | 14 +++++++-- src/domain.py | 66 +++++++++++++++++----------------------- src/settings.py | 42 +++++++++++++------------ src/utils/configpanel.py | 47 ++++++++++++---------------- 4 files changed, 81 insertions(+), 88 deletions(-) diff --git a/src/app.py b/src/app.py index ffcd1ecc3..08e222579 100644 --- a/src/app.py +++ b/src/app.py @@ -27,7 +27,7 @@ import subprocess import tempfile import copy from collections import OrderedDict -from typing import TYPE_CHECKING, List, Tuple, Dict, Any, Iterator, Optional +from typing import TYPE_CHECKING, List, Tuple, Dict, Any, Iterator, Optional, Union from packaging import version from logging import getLogger from pathlib import Path @@ -73,7 +73,10 @@ from yunohost.app_catalog import ( # noqa ) if TYPE_CHECKING: + from pydantic.typing import AbstractSetIntStr, MappingIntStrAny + from yunohost.utils.configpanel import ConfigPanelModel, RawSettings + from yunohost.utils.form import FormModel logger = getLogger("yunohost.app") @@ -1809,8 +1812,13 @@ class AppConfigPanel(ConfigPanel): def _get_raw_settings(self, config: "ConfigPanelModel") -> "RawSettings": return self._call_config_script("show") - def _apply(self): - env = {key: str(value) for key, value in self.new_values.items()} + def _apply( + self, + form: "FormModel", + previous_settings: dict[str, Any], + exclude: Union["AbstractSetIntStr", "MappingIntStrAny", None] = None, + ): + env = {key: str(value) for key, value in form.dict().items()} return_content = self._call_config_script("apply", env=env) # If the script returned validation error diff --git a/src/domain.py b/src/domain.py index 6d57a0f10..f0531e624 100644 --- a/src/domain.py +++ b/src/domain.py @@ -19,7 +19,7 @@ import os import time from pathlib import Path -from typing import TYPE_CHECKING, List, Optional +from typing import TYPE_CHECKING, Any, List, Optional, Union from collections import OrderedDict from logging import getLogger @@ -48,7 +48,10 @@ from yunohost.utils.dns import is_yunohost_dyndns_domain from yunohost.log import is_unit_operation if TYPE_CHECKING: + from pydantic.typing import AbstractSetIntStr, MappingIntStrAny + from yunohost.utils.configpanel import RawConfig + from yunohost.utils.form import FormModel logger = getLogger("yunohost.domain") @@ -669,7 +672,7 @@ class DomainConfigPanel(ConfigPanel): # Portal settings are only available on "topest" domains if _get_parent_domain_of(self.entity, topest=True) is not None: - del toml["feature"]["portal"] + del raw_config["feature"]["portal"] # Optimize wether or not to load the DNS section, # e.g. we don't want to trigger the whole _get_registary_config_section @@ -712,17 +715,23 @@ class DomainConfigPanel(ConfigPanel): return raw_config - def _apply(self): - if ( - "default_app" in self.future_values - and self.future_values["default_app"] != self.values["default_app"] - ): + def _apply( + self, + form: "FormModel", + previous_settings: dict[str, Any], + exclude: Union["AbstractSetIntStr", "MappingIntStrAny", None] = None, + ): + next_settings = { + k: v for k, v in form.dict().items() if previous_settings.get(k) != v + } + + if "default_app" in next_settings: from yunohost.app import app_ssowatconf, app_map if "/" in app_map(raw=True).get(self.entity, {}): raise YunohostValidationError( "app_make_default_location_already_used", - app=self.future_values["default_app"], + app=next_settings["default_app"], domain=self.entity, other_app=app_map(raw=True)[self.entity]["/"]["id"], ) @@ -735,8 +744,7 @@ class DomainConfigPanel(ConfigPanel): "portal_theme", ] if _get_parent_domain_of(self.entity, topest=True) is None and any( - option in self.future_values - and self.new_values[option] != self.values.get(option) + option in next_settings for option in portal_options ): from yunohost.portal import PORTAL_SETTINGS_DIR @@ -744,9 +752,8 @@ class DomainConfigPanel(ConfigPanel): # Portal options are also saved in a `domain.portal.yml` file # that can be read by the portal API. # FIXME remove those from the config panel saved values? - portal_values = { - option: self.future_values[option] for option in portal_options - } + + portal_values = form.dict(include=portal_options) portal_settings_path = Path(f"{PORTAL_SETTINGS_DIR}/{self.entity}.json") portal_settings = {"apps": {}} @@ -760,38 +767,21 @@ class DomainConfigPanel(ConfigPanel): str(portal_settings_path), portal_settings, sort_keys=True, indent=4 ) - super()._apply() + super()._apply(form, previous_settings) # Reload ssowat if default app changed - if ( - "default_app" in self.future_values - and self.future_values["default_app"] != self.values["default_app"] - ): + if "default_app" in next_settings: app_ssowatconf() - stuff_to_regen_conf = [] - if ( - "xmpp" in self.future_values - and self.future_values["xmpp"] != self.values["xmpp"] - ): - stuff_to_regen_conf.append("nginx") - stuff_to_regen_conf.append("metronome") + stuff_to_regen_conf = set() + if "xmpp" in next_settings: + stuff_to_regen_conf.update({"nginx", "metronome"}) - if ( - "mail_in" in self.future_values - and self.future_values["mail_in"] != self.values["mail_in"] - ) or ( - "mail_out" in self.future_values - and self.future_values["mail_out"] != self.values["mail_out"] - ): - if "nginx" not in stuff_to_regen_conf: - stuff_to_regen_conf.append("nginx") - stuff_to_regen_conf.append("postfix") - stuff_to_regen_conf.append("dovecot") - stuff_to_regen_conf.append("rspamd") + if "mail_in" in next_settings or "mail_out" in next_settings: + stuff_to_regen_conf.update({"nginx", "postfix", "dovecot", "rspamd"}) if stuff_to_regen_conf: - regen_conf(names=stuff_to_regen_conf) + regen_conf(names=list(stuff_to_regen_conf)) def domain_action_run(domain, action, args=None): diff --git a/src/settings.py b/src/settings.py index e66195802..6a05217dc 100644 --- a/src/settings.py +++ b/src/settings.py @@ -31,12 +31,15 @@ from yunohost.log import is_unit_operation from yunohost.utils.legacy import translate_legacy_settings_to_configpanel_settings if TYPE_CHECKING: + from pydantic.typing import AbstractSetIntStr, MappingIntStrAny + from yunohost.utils.configpanel import ( ConfigPanelGetMode, ConfigPanelModel, RawConfig, RawSettings, ) + from yunohost.utils.form import FormModel logger = getLogger("yunohost.settings") @@ -217,19 +220,15 @@ class SettingsConfigPanel(ConfigPanel): return raw_settings - def _apply(self): - root_password = self.new_values.pop("root_password", None) - root_password_confirm = self.new_values.pop("root_password_confirm", None) - passwordless_sudo = self.new_values.pop("passwordless_sudo", None) - - self.values = { - k: v for k, v in self.values.items() if k not in self.virtual_settings - } - self.new_values = { - k: v for k, v in self.new_values.items() if k not in self.virtual_settings - } - - assert all(v not in self.future_values for v in self.virtual_settings) + def _apply( + self, + form: "FormModel", + previous_settings: dict[str, Any], + exclude: Union["AbstractSetIntStr", "MappingIntStrAny", None] = None, + ): + root_password = form.get("root_password", None) + root_password_confirm = form.get("root_password_confirm", None) + passwordless_sudo = form.get("passwordless_sudo", None) if root_password and root_password.strip(): if root_password != root_password_confirm: @@ -248,15 +247,20 @@ class SettingsConfigPanel(ConfigPanel): {"sudoOption": ["!authenticate"] if passwordless_sudo else []}, ) - super()._apply() - - settings = { - k: v for k, v in self.future_values.items() if self.values.get(k) != v + # First save settings except virtual + default ones + super()._apply(form, previous_settings, exclude=self.virtual_settings) + next_settings = { + k: v + for k, v in form.dict(exclude=self.virtual_settings).items() + if previous_settings.get(k) != v } - for setting_name, value in settings.items(): + + for setting_name, value in next_settings.items(): try: + # FIXME not sure to understand why we need the previous value if + # updated_settings has already been filtered trigger_post_change_hook( - setting_name, self.values.get(setting_name), value + setting_name, previous_settings.get(setting_name), value ) except Exception as e: logger.error(f"Post-change hook for setting failed : {e}") diff --git a/src/utils/configpanel.py b/src/utils/configpanel.py index 22318e5e0..56b0584e3 100644 --- a/src/utils/configpanel.py +++ b/src/utils/configpanel.py @@ -47,6 +47,8 @@ from yunohost.utils.i18n import _value_for_locale if TYPE_CHECKING: from pydantic.fields import ModelField + from pydantic.typing import AbstractSetIntStr, MappingIntStrAny + from yunohost.utils.form import FormModel, Hooks logger = getLogger("yunohost.configpanel") @@ -678,43 +680,32 @@ class ConfigPanel: hooks=hooks, ) -<<<<<<< HEAD - # Check and ask unanswered questions - prefilled_answers = self.args.copy() - prefilled_answers.update(self.new_values) - - questions = ask_questions_and_parse_answers( - {question["id"]: question for question in section["options"]}, - prefilled_answers=prefilled_answers, - current_values=self.values, - hooks=self.hooks, - ) - self.new_values.update( - { - question.id: question.value - for question in questions - if not question.readonly and question.value is not None - } - ) -======= return settings ->>>>>>> be777b928 (configpanel: update _ask) - def _apply(self): + def _apply( + self, + form: "FormModel", + previous_settings: dict[str, Any], + exclude: Union["AbstractSetIntStr", "MappingIntStrAny", None] = None, + ) -> dict[str, Any]: + """ + Save settings in yaml file. + If `save_mode` is `"diff"` (which is the default), only values that are + different from their default value will be saved. + """ logger.info("Saving the new configuration...") + dir_path = os.path.dirname(os.path.realpath(self.save_path)) if not os.path.exists(dir_path): mkdir(dir_path, mode=0o700) - values_to_save = self.future_values - if self.save_mode == "diff": - defaults = self._get_default_values() - values_to_save = { - k: v for k, v in values_to_save.items() if defaults.get(k) != v - } + exclude_defaults = self.save_mode == "diff" + settings = form.dict(exclude_defaults=exclude_defaults, exclude=exclude) # type: ignore # Save the settings to the .yaml file - write_to_yaml(self.save_path, values_to_save) + write_to_yaml(self.save_path, settings) + + return settings def _reload_services(self): from yunohost.service import service_reload_or_restart