Merge branch 'enh-config-panel-file' of github.com:YunoHost/yunohost into enh-config-panel-file

This commit is contained in:
ljf 2021-09-04 19:11:41 +02:00
commit 9f9dfdbea7
7 changed files with 874 additions and 180 deletions

View file

@ -67,7 +67,7 @@ EOL
local source_key="$(echo "$source" | cut -d: -f1)" local source_key="$(echo "$source" | cut -d: -f1)"
source_key=${source_key:-$short_setting} source_key=${source_key:-$short_setting}
local source_file="$(echo "$source" | cut -d: -f2 | sed s@__FINALPATH__@$final_path@ | sed s/__APP__/$app/)" local source_file="$(echo "$source" | cut -d: -f2 | sed s@__FINALPATH__@$final_path@ | sed s/__APP__/$app/)"
old[$short_setting]="$(ynh_get_var --file="${source_file}" --key="${source_key}")" old[$short_setting]="$(ynh_read_var_in_file --file="${source_file}" --key="${source_key}")"
fi fi
done done
@ -130,7 +130,7 @@ _ynh_app_config_apply() {
local source_file="$(echo "$source" | cut -d: -f2 | sed s@__FINALPATH__@$final_path@ | sed s/__APP__/$app/)" local source_file="$(echo "$source" | cut -d: -f2 | sed s@__FINALPATH__@$final_path@ | sed s/__APP__/$app/)"
ynh_backup_if_checksum_is_different --file="$source_file" ynh_backup_if_checksum_is_different --file="$source_file"
ynh_set_var --file="${source_file}" --key="${source_key}" --value="${!short_setting}" ynh_write_var_in_file --file="${source_file}" --key="${source_key}" --value="${!short_setting}"
ynh_store_file_checksum --file="$source_file" --update_only ynh_store_file_checksum --file="$source_file" --update_only
# We stored the info in settings in order to be able to upgrade the app # We stored the info in settings in order to be able to upgrade the app

View file

@ -475,7 +475,7 @@ ynh_replace_vars () {
# Get a value from heterogeneous file (yaml, json, php, python...) # Get a value from heterogeneous file (yaml, json, php, python...)
# #
# usage: ynh_get_var --file=PATH --key=KEY # usage: ynh_read_var_in_file --file=PATH --key=KEY
# | arg: -f, --file= - the path to the file # | arg: -f, --file= - the path to the file
# | arg: -k, --key= - the key to get # | arg: -k, --key= - the key to get
# #
@ -504,8 +504,9 @@ ynh_replace_vars () {
# USER = 8102 # USER = 8102
# user = 'https://donate.local' # user = 'https://donate.local'
# CUSTOM['user'] = 'YunoHost' # CUSTOM['user'] = 'YunoHost'
#
# Requires YunoHost version 4.3 or higher. # Requires YunoHost version 4.3 or higher.
ynh_get_var() { ynh_read_var_in_file() {
# Declare an array to define the options of this helper. # Declare an array to define the options of this helper.
local legacy_args=fk local legacy_args=fk
local -A args_array=( [f]=file= [k]=key= ) local -A args_array=( [f]=file= [k]=key= )
@ -515,10 +516,9 @@ ynh_get_var() {
ynh_handle_getopts_args "$@" ynh_handle_getopts_args "$@"
local var_part='^[ \t]*\$?(\w*\[)?[ \t]*["'"']?${key}['"'"]?[ \t]*\]?[ \t]*[:=]>?[ \t]*' local var_part='^[ \t]*\$?(\w*\[)?[ \t]*["'"']?${key}['"'"]?[ \t]*\]?[ \t]*[:=]>?[ \t]*'
local crazy_value="$((grep -i -o -P '^[ \t]*\$?(\w*\[)?[ \t]*["'"']?${key}['"'"]?[ \t]*\]?[ \t]*[:=]>?[ \t]*\K.*(?=[ \t,\n;]*$)' ${file} || echo YNH_NULL) | head -n1)" local crazy_value="$(grep -i -o -P '^[ \t]*\$?(\w*\[)?[ \t]*["'"']?${key}['"'"]?[ \t]*\]?[ \t]*[:=]>?[ \t]*\K.*(?=[ \t,\n;]*$)' ${file} || echo YNH_NULL | head -n1)"
#"
local first_char="${crazy_value:0:1}" local first_char="${crazy_value:0:1}"
if [[ "$first_char" == '"' ]] ; then if [[ "$first_char" == '"' ]] ; then
echo "$crazy_value" | grep -m1 -o -P '"\K([^"](\\")?)*[^\\](?=")' | head -n1 | sed 's/\\"/"/g' echo "$crazy_value" | grep -m1 -o -P '"\K([^"](\\")?)*[^\\](?=")' | head -n1 | sed 's/\\"/"/g'
@ -531,13 +531,13 @@ ynh_get_var() {
# Set a value into heterogeneous file (yaml, json, php, python...) # Set a value into heterogeneous file (yaml, json, php, python...)
# #
# usage: ynh_set_var --file=PATH --key=KEY --value=VALUE # usage: ynh_write_var_in_file --file=PATH --key=KEY --value=VALUE
# | arg: -f, --file= - the path to the file # | arg: -f, --file= - the path to the file
# | arg: -k, --key= - the key to set # | arg: -k, --key= - the key to set
# | arg: -v, --value= - the value to set # | arg: -v, --value= - the value to set
# #
# Requires YunoHost version 4.3 or higher. # Requires YunoHost version 4.3 or higher.
ynh_set_var() { ynh_write_var_in_file() {
# Declare an array to define the options of this helper. # Declare an array to define the options of this helper.
local legacy_args=fkv local legacy_args=fkv
local -A args_array=( [f]=file= [k]=key= [v]=value=) local -A args_array=( [f]=file= [k]=key= [v]=value=)
@ -547,7 +547,7 @@ ynh_set_var() {
# Manage arguments with getopts # Manage arguments with getopts
ynh_handle_getopts_args "$@" ynh_handle_getopts_args "$@"
local var_part='[ \t]*\$?(\w*\[)?[ \t]*["'"']?${key}['"'"]?[ \t]*\]?[ \t]*[:=]>?[ \t]*' local var_part='[ \t]*\$?(\w*\[)?[ \t]*["'"']?${key}['"'"]?[ \t]*\]?[ \t]*[:=]>?[ \t]*'
local crazy_value="$(grep -i -o -P '^[ \t]*\$?(\w*\[)?[ \t]*["'"']?${key}['"'"]?[ \t]*\]?[ \t]*[:=]>?[ \t]*\K.*(?=[ \t,\n;]*$)' ${file} | head -n1)" local crazy_value="$(grep -i -o -P '^[ \t]*\$?(\w*\[)?[ \t]*["'"']?${key}['"'"]?[ \t]*\]?[ \t]*[:=]>?[ \t]*\K.*(?=[ \t,\n;]*$)' ${file} | head -n1)"
# local crazy_value="$(grep -i -o -P "^${var_part}\K.*(?=[ \t,\n;]*\$)" ${file} | head -n1)" # local crazy_value="$(grep -i -o -P "^${var_part}\K.*(?=[ \t,\n;]*\$)" ${file} | head -n1)"
local first_char="${crazy_value:0:1}" local first_char="${crazy_value:0:1}"

View file

@ -6,6 +6,7 @@ from yunohost.app import app_list
from yunohost.diagnosis import Diagnoser from yunohost.diagnosis import Diagnoser
class AppDiagnoser(Diagnoser): class AppDiagnoser(Diagnoser):
id_ = os.path.splitext(os.path.basename(__file__))[0].split("-")[1] id_ = os.path.splitext(os.path.basename(__file__))[0].split("-")[1]
@ -30,13 +31,17 @@ class AppDiagnoser(Diagnoser):
if not app["issues"]: if not app["issues"]:
continue continue
level = "ERROR" if any(issue[0] == "error" for issue in app["issues"]) else "WARNING" level = (
"ERROR"
if any(issue[0] == "error" for issue in app["issues"])
else "WARNING"
)
yield dict( yield dict(
meta={"test": "apps", "app": app["name"]}, meta={"test": "apps", "app": app["name"]},
status=level, status=level,
summary="diagnosis_apps_issue", summary="diagnosis_apps_issue",
details=[issue[1] for issue in app["issues"]] details=[issue[1] for issue in app["issues"]],
) )
def issues(self, app): def issues(self, app):
@ -45,14 +50,19 @@ class AppDiagnoser(Diagnoser):
if not app.get("from_catalog") or app["from_catalog"].get("state") != "working": if not app.get("from_catalog") or app["from_catalog"].get("state") != "working":
yield ("error", "diagnosis_apps_not_in_app_catalog") yield ("error", "diagnosis_apps_not_in_app_catalog")
elif not isinstance(app["from_catalog"].get("level"), int) or app["from_catalog"]["level"] == 0: elif (
not isinstance(app["from_catalog"].get("level"), int)
or app["from_catalog"]["level"] == 0
):
yield ("error", "diagnosis_apps_broken") yield ("error", "diagnosis_apps_broken")
elif app["from_catalog"]["level"] <= 4: elif app["from_catalog"]["level"] <= 4:
yield ("warning", "diagnosis_apps_bad_quality") yield ("warning", "diagnosis_apps_bad_quality")
# Check for super old, deprecated practices # Check for super old, deprecated practices
yunohost_version_req = app["manifest"].get("requirements", {}).get("yunohost", "").strip(">= ") yunohost_version_req = (
app["manifest"].get("requirements", {}).get("yunohost", "").strip(">= ")
)
if yunohost_version_req.startswith("2."): if yunohost_version_req.startswith("2."):
yield ("error", "diagnosis_apps_outdated_ynh_requirement") yield ("error", "diagnosis_apps_outdated_ynh_requirement")
@ -64,11 +74,21 @@ class AppDiagnoser(Diagnoser):
"yunohost tools port-available", "yunohost tools port-available",
] ]
for deprecated_helper in deprecated_helpers: for deprecated_helper in deprecated_helpers:
if os.system(f"grep -nr -q '{deprecated_helper}' {app['setting_path']}/scripts/") == 0: if (
os.system(
f"grep -nr -q '{deprecated_helper}' {app['setting_path']}/scripts/"
)
== 0
):
yield ("error", "diagnosis_apps_deprecated_practices") yield ("error", "diagnosis_apps_deprecated_practices")
old_arg_regex = r'^domain=\${?[0-9]' old_arg_regex = r"^domain=\${?[0-9]"
if os.system(f"grep -q '{old_arg_regex}' {app['setting_path']}/scripts/install") == 0: if (
os.system(
f"grep -q '{old_arg_regex}' {app['setting_path']}/scripts/install"
)
== 0
):
yield ("error", "diagnosis_apps_deprecated_practices") yield ("error", "diagnosis_apps_deprecated_practices")

View file

@ -36,7 +36,6 @@ import urllib.parse
import tempfile import tempfile
from collections import OrderedDict from collections import OrderedDict
from moulinette.interfaces.cli import colorize
from moulinette import Moulinette, m18n from moulinette import Moulinette, m18n
from moulinette.core import MoulinetteError from moulinette.core import MoulinetteError
from moulinette.utils.log import getActionLogger from moulinette.utils.log import getActionLogger
@ -55,7 +54,11 @@ from moulinette.utils.filesystem import (
from yunohost.service import service_status, _run_service_command from yunohost.service import service_status, _run_service_command
from yunohost.utils import packages, config from yunohost.utils import packages, config
from yunohost.utils.config import ConfigPanel, parse_args_in_yunohost_format, Question from yunohost.utils.config import (
ConfigPanel,
parse_args_in_yunohost_format,
Question,
)
from yunohost.utils.i18n import _value_for_locale from yunohost.utils.i18n import _value_for_locale
from yunohost.utils.error import YunohostError, YunohostValidationError from yunohost.utils.error import YunohostError, YunohostValidationError
from yunohost.utils.filesystem import free_space_in_directory from yunohost.utils.filesystem import free_space_in_directory
@ -1756,30 +1759,33 @@ def app_action_run(operation_logger, app, action, args=None):
return logger.success("Action successed!") return logger.success("Action successed!")
def app_config_get(app, key='', mode='classic'): def app_config_get(app, key="", mode="classic"):
""" """
Display an app configuration in classic, full or export mode Display an app configuration in classic, full or export mode
""" """
config = AppConfigPanel(app) config_ = AppConfigPanel(app)
return config.get(key, mode) return config_.get(key, mode)
@is_unit_operation() @is_unit_operation()
def app_config_set(operation_logger, app, key=None, value=None, args=None, args_file=None): def app_config_set(
operation_logger, app, key=None, value=None, args=None, args_file=None
):
""" """
Apply a new app configuration Apply a new app configuration
""" """
config = AppConfigPanel(app) config_ = AppConfigPanel(app)
Question.operation_logger = operation_logger Question.operation_logger = operation_logger
operation_logger.start() operation_logger.start()
result = config.set(key, value, args, args_file) result = config_.set(key, value, args, args_file)
if "errors" not in result: if "errors" not in result:
operation_logger.success() operation_logger.success()
return result return result
class AppConfigPanel(ConfigPanel): class AppConfigPanel(ConfigPanel):
def __init__(self, app): def __init__(self, app):
@ -1791,10 +1797,10 @@ class AppConfigPanel(ConfigPanel):
super().__init__(config_path=config_path) super().__init__(config_path=config_path)
def _load_current_values(self): def _load_current_values(self):
self.values = self._call_config_script('show') self.values = self._call_config_script("show")
def _apply(self): def _apply(self):
self.errors = self._call_config_script('apply', self.new_values) self.errors = self._call_config_script("apply", self.new_values)
def _call_config_script(self, action, env={}): def _call_config_script(self, action, env={}):
from yunohost.hook import hook_exec from yunohost.hook import hook_exec
@ -1814,22 +1820,23 @@ ynh_app_config_run $1
# Call config script to extract current values # Call config script to extract current values
logger.debug(f"Calling '{action}' action from config script") logger.debug(f"Calling '{action}' action from config script")
app_id, app_instance_nb = _parse_app_instance_name(self.app) app_id, app_instance_nb = _parse_app_instance_name(self.app)
env.update({ env.update(
"app_id": app_id, {
"app": self.app, "app_id": app_id,
"app_instance_nb": str(app_instance_nb), "app": self.app,
}) "app_instance_nb": str(app_instance_nb),
}
ret, values = hook_exec(
config_script, args=[action], env=env
) )
ret, values = hook_exec(config_script, args=[action], env=env)
if ret != 0: if ret != 0:
if action == 'show': if action == "show":
raise YunohostError("app_config_unable_to_read_values") raise YunohostError("app_config_unable_to_read_values")
else: else:
raise YunohostError("app_config_unable_to_apply_values_correctly") raise YunohostError("app_config_unable_to_apply_values_correctly")
return values return values
def _get_all_installed_apps_id(): def _get_all_installed_apps_id():
""" """
Return something like: Return something like:
@ -2455,9 +2462,6 @@ def _parse_args_for_action(action, args={}):
return parse_args_in_yunohost_format(args, action_args) return parse_args_in_yunohost_format(args, action_args)
def _validate_and_normalize_webpath(args_dict, app_folder): def _validate_and_normalize_webpath(args_dict, app_folder):
# If there's only one "domain" and "path", validate that domain/path # If there's only one "domain" and "path", validate that domain/path

View file

@ -21,9 +21,9 @@
import os import os
import re import re
import toml
import urllib.parse import urllib.parse
import tempfile import tempfile
import shutil
from collections import OrderedDict from collections import OrderedDict
from moulinette.interfaces.cli import colorize from moulinette.interfaces.cli import colorize
@ -37,16 +37,14 @@ from moulinette.utils.filesystem import (
mkdir, mkdir,
) )
from yunohost.service import _get_services
from yunohost.service import _run_service_command, _get_services
from yunohost.utils.i18n import _value_for_locale from yunohost.utils.i18n import _value_for_locale
from yunohost.utils.error import YunohostError, YunohostValidationError from yunohost.utils.error import YunohostError, YunohostValidationError
logger = getActionLogger("yunohost.config") logger = getActionLogger("yunohost.config")
CONFIG_PANEL_VERSION_SUPPORTED = 1.0 CONFIG_PANEL_VERSION_SUPPORTED = 1.0
class ConfigPanel:
class ConfigPanel:
def __init__(self, config_path, save_path=None): def __init__(self, config_path, save_path=None):
self.config_path = config_path self.config_path = config_path
self.save_path = save_path self.save_path = save_path
@ -54,8 +52,8 @@ class ConfigPanel:
self.values = {} self.values = {}
self.new_values = {} self.new_values = {}
def get(self, key='', mode='classic'): def get(self, key="", mode="classic"):
self.filter_key = key or '' self.filter_key = key or ""
# Read config panel toml # Read config panel toml
self._get_config_panel() self._get_config_panel()
@ -68,12 +66,12 @@ class ConfigPanel:
self._hydrate() self._hydrate()
# Format result in full mode # Format result in full mode
if mode == 'full': if mode == "full":
return self.config return self.config
# In 'classic' mode, we display the current value if key refer to an option # In 'classic' mode, we display the current value if key refer to an option
if self.filter_key.count('.') == 2 and mode == 'classic': if self.filter_key.count(".") == 2 and mode == "classic":
option = self.filter_key.split('.')[-1] option = self.filter_key.split(".")[-1]
return self.values.get(option, None) return self.values.get(option, None)
# Format result in 'classic' or 'export' mode # Format result in 'classic' or 'export' mode
@ -81,17 +79,17 @@ class ConfigPanel:
result = {} result = {}
for panel, section, option in self._iterate(): for panel, section, option in self._iterate():
key = f"{panel['id']}.{section['id']}.{option['id']}" key = f"{panel['id']}.{section['id']}.{option['id']}"
if mode == 'export': if mode == "export":
result[option['id']] = option.get('current_value') result[option["id"]] = option.get("current_value")
else: else:
result[key] = { 'ask': _value_for_locale(option['ask']) } result[key] = {"ask": _value_for_locale(option["ask"])}
if 'current_value' in option: if "current_value" in option:
result[key]['value'] = option['current_value'] result[key]["value"] = option["current_value"]
return result return result
def set(self, key=None, value=None, args=None, args_file=None): def set(self, key=None, value=None, args=None, args_file=None):
self.filter_key = key or '' self.filter_key = key or ""
# Read config panel toml # Read config panel toml
self._get_config_panel() self._get_config_panel()
@ -102,20 +100,20 @@ class ConfigPanel:
if (args is not None or args_file is not None) and value is not None: if (args is not None or args_file is not None) and value is not None:
raise YunohostError("config_args_value") raise YunohostError("config_args_value")
if self.filter_key.count('.') != 2 and not value is None: if self.filter_key.count(".") != 2 and not value is None:
raise YunohostError("config_set_value_on_section") raise YunohostError("config_set_value_on_section")
# Import and parse pre-answered options # Import and parse pre-answered options
logger.debug("Import and parse pre-answered options") logger.debug("Import and parse pre-answered options")
args = urllib.parse.parse_qs(args or '', keep_blank_values=True) args = urllib.parse.parse_qs(args or "", keep_blank_values=True)
self.args = { key: ','.join(value_) for key, value_ in args.items() } self.args = {key: ",".join(value_) for key, value_ in args.items()}
if args_file: if args_file:
# Import YAML / JSON file but keep --args values # Import YAML / JSON file but keep --args values
self.args = { **read_yaml(args_file), **self.args } self.args = {**read_yaml(args_file), **self.args}
if value is not None: if value is not None:
self.args = {self.filter_key.split('.')[-1]: value} self.args = {self.filter_key.split(".")[-1]: value}
# Read or get values and hydrate the config # Read or get values and hydrate the config
self._load_current_values() self._load_current_values()
@ -144,7 +142,7 @@ class ConfigPanel:
if self.errors: if self.errors:
return { return {
"errors": errors, "errors": self.errors,
} }
self._reload_services() self._reload_services()
@ -155,10 +153,9 @@ class ConfigPanel:
def _get_toml(self): def _get_toml(self):
return read_toml(self.config_path) return read_toml(self.config_path)
def _get_config_panel(self): def _get_config_panel(self):
# Split filter_key # Split filter_key
filter_key = dict(enumerate(self.filter_key.split('.'))) filter_key = dict(enumerate(self.filter_key.split(".")))
if len(filter_key) > 3: if len(filter_key) > 3:
raise YunohostError("config_too_much_sub_keys") raise YunohostError("config_too_much_sub_keys")
@ -174,20 +171,18 @@ class ConfigPanel:
# Transform toml format into internal format # Transform toml format into internal format
defaults = { defaults = {
'toml': { "toml": {"version": 1.0},
'version': 1.0 "panels": {
}, "name": "",
'panels': { "services": [],
'name': '', "actions": {"apply": {"en": "Apply"}},
'services': [], }, # help
'actions': {'apply': {'en': 'Apply'}} "sections": {
}, # help "name": "",
'sections': { "services": [],
'name': '', "optional": True,
'services': [], }, # visibleIf help
'optional': True "options": {}
}, # visibleIf help
'options': {}
# ask type source help helpLink example style icon placeholder visibleIf # ask type source help helpLink example style icon placeholder visibleIf
# optional choices pattern limit min max step accept redact # optional choices pattern limit min max step accept redact
} }
@ -207,30 +202,36 @@ class ConfigPanel:
# Define the filter_key part to use and the children type # Define the filter_key part to use and the children type
i = list(defaults).index(node_type) i = list(defaults).index(node_type)
search_key = filter_key.get(i) search_key = filter_key.get(i)
subnode_type = list(defaults)[i+1] if node_type != 'options' else None subnode_type = list(defaults)[i + 1] if node_type != "options" else None
for key, value in toml_node.items(): for key, value in toml_node.items():
# Key/value are a child node # Key/value are a child node
if isinstance(value, OrderedDict) and key not in default and subnode_type: if (
isinstance(value, OrderedDict)
and key not in default
and subnode_type
):
# We exclude all nodes not referenced by the filter_key # We exclude all nodes not referenced by the filter_key
if search_key and key != search_key: if search_key and key != search_key:
continue continue
subnode = convert(value, subnode_type) subnode = convert(value, subnode_type)
subnode['id'] = key subnode["id"] = key
if node_type == 'sections': if node_type == "sections":
subnode['name'] = key # legacy subnode["name"] = key # legacy
subnode.setdefault('optional', toml_node.get('optional', True)) subnode.setdefault("optional", toml_node.get("optional", True))
node.setdefault(subnode_type, []).append(subnode) node.setdefault(subnode_type, []).append(subnode)
# Key/value are a property # Key/value are a property
else: else:
# Todo search all i18n keys # Todo search all i18n keys
node[key] = value if key not in ['ask', 'help', 'name'] else { 'en': value } node[key] = (
value if key not in ["ask", "help", "name"] else {"en": value}
)
return node return node
self.config = convert(toml_config_panel, 'toml') self.config = convert(toml_config_panel, "toml")
try: try:
self.config['panels'][0]['sections'][0]['options'][0] self.config["panels"][0]["sections"][0]["options"][0]
except (KeyError, IndexError): except (KeyError, IndexError):
raise YunohostError( raise YunohostError(
"config_empty_or_bad_filter_key", filter_key=self.filter_key "config_empty_or_bad_filter_key", filter_key=self.filter_key
@ -242,36 +243,41 @@ class ConfigPanel:
# Hydrating config panel with current value # Hydrating config panel with current value
logger.debug("Hydrating config with current values") logger.debug("Hydrating config with current values")
for _, _, option in self._iterate(): for _, _, option in self._iterate():
if option['name'] not in self.values: if option["name"] not in self.values:
continue continue
value = self.values[option['name']] value = self.values[option["name"]]
# In general, the value is just a simple value. # In general, the value is just a simple value.
# Sometimes it could be a dict used to overwrite the option itself # Sometimes it could be a dict used to overwrite the option itself
value = value if isinstance(value, dict) else {'current_value': value } value = value if isinstance(value, dict) else {"current_value": value}
option.update(value) option.update(value)
return self.values return self.values
def _ask(self): def _ask(self):
logger.debug("Ask unanswered question and prevalidate data") logger.debug("Ask unanswered question and prevalidate data")
def display_header(message): def display_header(message):
""" CLI panel/section header display """CLI panel/section header display"""
""" if Moulinette.interface.type == "cli" and self.filter_key.count(".") < 2:
if Moulinette.interface.type == 'cli' and self.filter_key.count('.') < 2: Moulinette.display(colorize(message, "purple"))
Moulinette.display(colorize(message, 'purple'))
for panel, section, obj in self._iterate(['panel', 'section']): for panel, section, obj in self._iterate(["panel", "section"]):
if panel == obj: if panel == obj:
name = _value_for_locale(panel['name']) name = _value_for_locale(panel["name"])
display_header(f"\n{'='*40}\n>>>> {name}\n{'='*40}") display_header(f"\n{'='*40}\n>>>> {name}\n{'='*40}")
continue continue
name = _value_for_locale(section['name']) name = _value_for_locale(section["name"])
display_header(f"\n# {name}") display_header(f"\n# {name}")
# Check and ask unanswered questions # Check and ask unanswered questions
self.new_values.update(parse_args_in_yunohost_format( self.new_values.update(
self.args, section['options'] parse_args_in_yunohost_format(self.args, section["options"])
)) )
self.new_values = {key: str(value[0]) for key, value in self.new_values.items() if not value[0] is None} self.new_values = {
key: str(value[0])
for key, value in self.new_values.items()
if not value[0] is None
}
def _apply(self): def _apply(self):
logger.info("Running config script...") logger.info("Running config script...")
@ -281,36 +287,37 @@ class ConfigPanel:
# Save the settings to the .yaml file # Save the settings to the .yaml file
write_to_yaml(self.save_path, self.new_values) write_to_yaml(self.save_path, self.new_values)
def _reload_services(self): def _reload_services(self):
from yunohost.service import _run_service_command, _get_services
logger.info("Reloading services...") logger.info("Reloading services...")
services_to_reload = set() services_to_reload = set()
for panel, section, obj in self._iterate(['panel', 'section', 'option']): for panel, section, obj in self._iterate(["panel", "section", "option"]):
services_to_reload |= set(obj.get('services', [])) services_to_reload |= set(obj.get("services", []))
services_to_reload = list(services_to_reload) services_to_reload = list(services_to_reload)
services_to_reload.sort(key = 'nginx'.__eq__) services_to_reload.sort(key="nginx".__eq__)
for service in services_to_reload: for service in services_to_reload:
if '__APP__': if "__APP__":
service = service.replace('__APP__', self.app) service = service.replace("__APP__", self.app)
logger.debug(f"Reloading {service}") logger.debug(f"Reloading {service}")
if not _run_service_command('reload-or-restart', service): if not _run_service_command("reload-or-restart", service):
services = _get_services() services = _get_services()
test_conf = services[service].get('test_conf', 'true') test_conf = services[service].get("test_conf", "true")
errors = check_output(f"{test_conf}; exit 0") if test_conf else '' errors = check_output(f"{test_conf}; exit 0") if test_conf else ""
raise YunohostError( raise YunohostError(
"config_failed_service_reload", "config_failed_service_reload", service=service, errors=errors
service=service, errors=errors
) )
def _iterate(self, trigger=['option']): def _iterate(self, trigger=["option"]):
for panel in self.config.get("panels", []): for panel in self.config.get("panels", []):
if 'panel' in trigger: if "panel" in trigger:
yield (panel, None, panel) yield (panel, None, panel)
for section in panel.get("sections", []): for section in panel.get("sections", []):
if 'section' in trigger: if "section" in trigger:
yield (panel, section, section) yield (panel, section, section)
if 'option' in trigger: if "option" in trigger:
for option in section.get("options", []): for option in section.get("options", []):
yield (panel, section, option) yield (panel, section, option)
@ -327,17 +334,17 @@ class YunoHostArgumentFormatParser(object):
parsed_question = Question() parsed_question = Question()
parsed_question.name = question["name"] parsed_question.name = question["name"]
parsed_question.type = question.get("type", 'string') parsed_question.type = question.get("type", "string")
parsed_question.default = question.get("default", None) parsed_question.default = question.get("default", None)
parsed_question.current_value = question.get("current_value") parsed_question.current_value = question.get("current_value")
parsed_question.optional = question.get("optional", False) parsed_question.optional = question.get("optional", False)
parsed_question.choices = question.get("choices", []) parsed_question.choices = question.get("choices", [])
parsed_question.pattern = question.get("pattern") parsed_question.pattern = question.get("pattern")
parsed_question.ask = question.get("ask", {'en': f"{parsed_question.name}"}) parsed_question.ask = question.get("ask", {"en": f"{parsed_question.name}"})
parsed_question.help = question.get("help") parsed_question.help = question.get("help")
parsed_question.helpLink = question.get("helpLink") parsed_question.helpLink = question.get("helpLink")
parsed_question.value = user_answers.get(parsed_question.name) parsed_question.value = user_answers.get(parsed_question.name)
parsed_question.redact = question.get('redact', False) parsed_question.redact = question.get("redact", False)
# Empty value is parsed as empty string # Empty value is parsed as empty string
if parsed_question.default == "": if parsed_question.default == "":
@ -350,7 +357,7 @@ class YunoHostArgumentFormatParser(object):
while True: while True:
# Display question if no value filled or if it's a readonly message # Display question if no value filled or if it's a readonly message
if Moulinette.interface.type== 'cli': if Moulinette.interface.type == "cli":
text_for_user_input_in_cli = self._format_text_for_user_input_in_cli( text_for_user_input_in_cli = self._format_text_for_user_input_in_cli(
question question
) )
@ -368,10 +375,9 @@ class YunoHostArgumentFormatParser(object):
is_password=self.hide_user_input_in_prompt, is_password=self.hide_user_input_in_prompt,
confirm=self.hide_user_input_in_prompt, confirm=self.hide_user_input_in_prompt,
prefill=prefill, prefill=prefill,
is_multiline=(question.type == "text") is_multiline=(question.type == "text"),
) )
# Apply default value # Apply default value
if question.value in [None, ""] and question.default is not None: if question.value in [None, ""] and question.default is not None:
question.value = ( question.value = (
@ -384,9 +390,9 @@ class YunoHostArgumentFormatParser(object):
try: try:
self._prevalidate(question) self._prevalidate(question)
except YunohostValidationError as e: except YunohostValidationError as e:
if Moulinette.interface.type== 'api': if Moulinette.interface.type == "api":
raise raise
Moulinette.display(str(e), 'error') Moulinette.display(str(e), "error")
question.value = None question.value = None
continue continue
break break
@ -398,17 +404,17 @@ class YunoHostArgumentFormatParser(object):
def _prevalidate(self, question): def _prevalidate(self, question):
if question.value in [None, ""] and not question.optional: if question.value in [None, ""] and not question.optional:
raise YunohostValidationError( raise YunohostValidationError("app_argument_required", name=question.name)
"app_argument_required", name=question.name
)
# we have an answer, do some post checks # we have an answer, do some post checks
if question.value is not None: if question.value is not None:
if question.choices and question.value not in question.choices: if question.choices and question.value not in question.choices:
self._raise_invalid_answer(question) self._raise_invalid_answer(question)
if question.pattern and not re.match(question.pattern['regexp'], str(question.value)): if question.pattern and not re.match(
question.pattern["regexp"], str(question.value)
):
raise YunohostValidationError( raise YunohostValidationError(
question.pattern['error'], question.pattern["error"],
name=question.name, name=question.name,
value=question.value, value=question.value,
) )
@ -434,7 +440,7 @@ class YunoHostArgumentFormatParser(object):
text_for_user_input_in_cli += _value_for_locale(question.help) text_for_user_input_in_cli += _value_for_locale(question.help)
if question.helpLink: if question.helpLink:
if not isinstance(question.helpLink, dict): if not isinstance(question.helpLink, dict):
question.helpLink = {'href': question.helpLink} question.helpLink = {"href": question.helpLink}
text_for_user_input_in_cli += f"\n - See {question.helpLink['href']}" text_for_user_input_in_cli += f"\n - See {question.helpLink['href']}"
return text_for_user_input_in_cli return text_for_user_input_in_cli
@ -467,18 +473,18 @@ class StringArgumentParser(YunoHostArgumentFormatParser):
argument_type = "string" argument_type = "string"
default_value = "" default_value = ""
class TagsArgumentParser(YunoHostArgumentFormatParser): class TagsArgumentParser(YunoHostArgumentFormatParser):
argument_type = "tags" argument_type = "tags"
def _prevalidate(self, question): def _prevalidate(self, question):
values = question.value values = question.value
for value in values.split(','): for value in values.split(","):
question.value = value question.value = value
super()._prevalidate(question) super()._prevalidate(question)
question.value = values question.value = values
class PasswordArgumentParser(YunoHostArgumentFormatParser): class PasswordArgumentParser(YunoHostArgumentFormatParser):
hide_user_input_in_prompt = True hide_user_input_in_prompt = True
argument_type = "password" argument_type = "password"
@ -522,9 +528,7 @@ class BooleanArgumentParser(YunoHostArgumentFormatParser):
default_value = False default_value = False
def parse_question(self, question, user_answers): def parse_question(self, question, user_answers):
question = super().parse_question( question = super().parse_question(question, user_answers)
question, user_answers
)
if question.default is None: if question.default is None:
question.default = False question.default = False
@ -616,11 +620,9 @@ class NumberArgumentParser(YunoHostArgumentFormatParser):
default_value = "" default_value = ""
def parse_question(self, question, user_answers): def parse_question(self, question, user_answers):
question_parsed = super().parse_question( question_parsed = super().parse_question(question, user_answers)
question, user_answers question_parsed.min = question.get("min", None)
) question_parsed.max = question.get("max", None)
question_parsed.min = question.get('min', None)
question_parsed.max = question.get('max', None)
if question_parsed.default is None: if question_parsed.default is None:
question_parsed.default = 0 question_parsed.default = 0
@ -628,19 +630,27 @@ class NumberArgumentParser(YunoHostArgumentFormatParser):
def _prevalidate(self, question): def _prevalidate(self, question):
super()._prevalidate(question) super()._prevalidate(question)
if not isinstance(question.value, int) and not (isinstance(question.value, str) and question.value.isdigit()): if not isinstance(question.value, int) and not (
isinstance(question.value, str) and question.value.isdigit()
):
raise YunohostValidationError( raise YunohostValidationError(
"app_argument_invalid", field=question.name, error=m18n.n("invalid_number") "app_argument_invalid",
field=question.name,
error=m18n.n("invalid_number"),
) )
if question.min is not None and int(question.value) < question.min: if question.min is not None and int(question.value) < question.min:
raise YunohostValidationError( raise YunohostValidationError(
"app_argument_invalid", field=question.name, error=m18n.n("invalid_number") "app_argument_invalid",
field=question.name,
error=m18n.n("invalid_number"),
) )
if question.max is not None and int(question.value) > question.max: if question.max is not None and int(question.value) > question.max:
raise YunohostValidationError( raise YunohostValidationError(
"app_argument_invalid", field=question.name, error=m18n.n("invalid_number") "app_argument_invalid",
field=question.name,
error=m18n.n("invalid_number"),
) )
def _post_parse_value(self, question): def _post_parse_value(self, question):
@ -660,29 +670,28 @@ class DisplayTextArgumentParser(YunoHostArgumentFormatParser):
readonly = True readonly = True
def parse_question(self, question, user_answers): def parse_question(self, question, user_answers):
question_parsed = super().parse_question( question_parsed = super().parse_question(question, user_answers)
question, user_answers
)
question_parsed.optional = True question_parsed.optional = True
question_parsed.style = question.get('style', 'info') question_parsed.style = question.get("style", "info")
return question_parsed return question_parsed
def _format_text_for_user_input_in_cli(self, question): def _format_text_for_user_input_in_cli(self, question):
text = question.ask['en'] text = question.ask["en"]
if question.style in ['success', 'info', 'warning', 'danger']: if question.style in ["success", "info", "warning", "danger"]:
color = { color = {
'success': 'green', "success": "green",
'info': 'cyan', "info": "cyan",
'warning': 'yellow', "warning": "yellow",
'danger': 'red' "danger": "red",
} }
return colorize(m18n.g(question.style), color[question.style]) + f" {text}" return colorize(m18n.g(question.style), color[question.style]) + f" {text}"
else: else:
return text return text
class FileArgumentParser(YunoHostArgumentFormatParser): class FileArgumentParser(YunoHostArgumentFormatParser):
argument_type = "file" argument_type = "file"
upload_dirs = [] upload_dirs = []
@ -690,60 +699,77 @@ class FileArgumentParser(YunoHostArgumentFormatParser):
@classmethod @classmethod
def clean_upload_dirs(cls): def clean_upload_dirs(cls):
# Delete files uploaded from API # Delete files uploaded from API
if Moulinette.interface.type== 'api': if Moulinette.interface.type == "api":
for upload_dir in cls.upload_dirs: for upload_dir in cls.upload_dirs:
if os.path.exists(upload_dir): if os.path.exists(upload_dir):
shutil.rmtree(upload_dir) shutil.rmtree(upload_dir)
def parse_question(self, question, user_answers): def parse_question(self, question, user_answers):
question_parsed = super().parse_question( question_parsed = super().parse_question(question, user_answers)
question, user_answers if question.get("accept"):
) question_parsed.accept = question.get("accept").replace(" ", "").split(",")
if question.get('accept'):
question_parsed.accept = question.get('accept').replace(' ', '').split(',')
else: else:
question_parsed.accept = [] question_parsed.accept = []
if Moulinette.interface.type== 'api': if Moulinette.interface.type == "api":
if user_answers.get(f"{question_parsed.name}[name]"): if user_answers.get(f"{question_parsed.name}[name]"):
question_parsed.value = { question_parsed.value = {
'content': question_parsed.value, "content": question_parsed.value,
'filename': user_answers.get(f"{question_parsed.name}[name]", question_parsed.name), "filename": user_answers.get(
f"{question_parsed.name}[name]", question_parsed.name
),
} }
# If path file are the same # If path file are the same
if question_parsed.value and str(question_parsed.value) == question_parsed.current_value: if (
question_parsed.value
and str(question_parsed.value) == question_parsed.current_value
):
question_parsed.value = None question_parsed.value = None
return question_parsed return question_parsed
def _prevalidate(self, question): def _prevalidate(self, question):
super()._prevalidate(question) super()._prevalidate(question)
if isinstance(question.value, str) and question.value and not os.path.exists(question.value): if (
isinstance(question.value, str)
and question.value
and not os.path.exists(question.value)
):
raise YunohostValidationError( raise YunohostValidationError(
"app_argument_invalid", field=question.name, error=m18n.n("invalid_number1") "app_argument_invalid",
field=question.name,
error=m18n.n("invalid_number1"),
) )
if question.value in [None, ''] or not question.accept: if question.value in [None, ""] or not question.accept:
return return
filename = question.value if isinstance(question.value, str) else question.value['filename'] filename = (
if '.' not in filename or '.' + filename.split('.')[-1] not in question.accept: question.value
if isinstance(question.value, str)
else question.value["filename"]
)
if "." not in filename or "." + filename.split(".")[-1] not in question.accept:
raise YunohostValidationError( raise YunohostValidationError(
"app_argument_invalid", field=question.name, error=m18n.n("invalid_number2") "app_argument_invalid",
field=question.name,
error=m18n.n("invalid_number2"),
) )
def _post_parse_value(self, question): def _post_parse_value(self, question):
from base64 import b64decode from base64 import b64decode
# Upload files from API # Upload files from API
# A file arg contains a string with "FILENAME:BASE64_CONTENT" # A file arg contains a string with "FILENAME:BASE64_CONTENT"
if not question.value: if not question.value:
return question.value return question.value
if Moulinette.interface.type== 'api': if Moulinette.interface.type == "api":
upload_dir = tempfile.mkdtemp(prefix='tmp_configpanel_') upload_dir = tempfile.mkdtemp(prefix="tmp_configpanel_")
FileArgumentParser.upload_dirs += [upload_dir] FileArgumentParser.upload_dirs += [upload_dir]
filename = question.value['filename'] filename = question.value["filename"]
logger.debug(f"Save uploaded file {question.value['filename']} from API into {upload_dir}") logger.debug(
f"Save uploaded file {question.value['filename']} from API into {upload_dir}"
)
# Filename is given by user of the API. For security reason, we have replaced # Filename is given by user of the API. For security reason, we have replaced
# os.path.join to avoid the user to be able to rewrite a file in filesystem # os.path.join to avoid the user to be able to rewrite a file in filesystem
@ -755,9 +781,9 @@ class FileArgumentParser(YunoHostArgumentFormatParser):
while os.path.exists(file_path): while os.path.exists(file_path):
file_path = os.path.normpath(upload_dir + "/" + filename + (".%d" % i)) file_path = os.path.normpath(upload_dir + "/" + filename + (".%d" % i))
i += 1 i += 1
content = question.value['content'] content = question.value["content"]
try: try:
with open(file_path, 'wb') as f: with open(file_path, "wb") as f:
f.write(b64decode(content)) f.write(b64decode(content))
except IOError as e: except IOError as e:
raise YunohostError("cannot_write_file", file=file_path, error=str(e)) raise YunohostError("cannot_write_file", file=file_path, error=str(e))
@ -790,6 +816,7 @@ ARGUMENTS_TYPE_PARSERS = {
"file": FileArgumentParser, "file": FileArgumentParser,
} }
def parse_args_in_yunohost_format(user_answers, argument_questions): def parse_args_in_yunohost_format(user_answers, argument_questions):
"""Parse arguments store in either manifest.json or actions.json or from a """Parse arguments store in either manifest.json or actions.json or from a
config panel against the user answers when they are present. config panel against the user answers when they are present.
@ -811,4 +838,3 @@ def parse_args_in_yunohost_format(user_answers, argument_questions):
parsed_answers_dict[question["name"]] = answer parsed_answers_dict[question["name"]] = answer
return parsed_answers_dict return parsed_answers_dict

View file

@ -18,7 +18,8 @@
along with this program; if not, see http://www.gnu.org/licenses along with this program; if not, see http://www.gnu.org/licenses
""" """
from moulinette import Moulinette, m18n from moulinette import m18n
def _value_for_locale(values): def _value_for_locale(values):
""" """
@ -42,5 +43,3 @@ def _value_for_locale(values):
# Fallback to first value # Fallback to first value
return list(values.values())[0] return list(values.values())[0]

View file

@ -0,0 +1,645 @@
#################
# _ __ _ _ #
# | '_ \| | | | #
# | |_) | |_| | #
# | .__/ \__, | #
# | | __/ | #
# |_| |___/ #
# #
#################
_read_py() {
local file="$1"
local key="$2"
python3 -c "exec(open('$file').read()); print($key)"
}
ynhtest_config_read_py() {
local dummy_dir="$(mktemp -d -p $VAR_WWW)"
file="$dummy_dir/dummy.py"
cat << EOF > $dummy_dir/dummy.py
# Some comment
FOO = None
ENABLED = False
# TITLE = "Old title"
TITLE = "Lorem Ipsum"
THEME = "colib'ris"
EMAIL = "root@example.com"
PORT = 1234
URL = 'https://yunohost.org'
DICT = {}
DICT['ldap_base'] = "ou=users,dc=yunohost,dc=org"
EOF
test "$(_read_py "$file" "FOO")" == "None"
test "$(ynh_read_var_in_file "$file" "FOO")" == "None"
test "$(_read_py "$file" "ENABLED")" == "False"
test "$(ynh_read_var_in_file "$file" "ENABLED")" == "False"
test "$(_read_py "$file" "TITLE")" == "Lorem Ipsum"
test "$(ynh_read_var_in_file "$file" "TITLE")" == "Lorem Ipsum"
test "$(_read_py "$file" "THEME")" == "colib'ris"
test "$(ynh_read_var_in_file "$file" "THEME")" == "colib'ris"
test "$(_read_py "$file" "EMAIL")" == "root@example.com"
test "$(ynh_read_var_in_file "$file" "EMAIL")" == "root@example.com"
test "$(_read_py "$file" "PORT")" == "1234"
test "$(ynh_read_var_in_file "$file" "PORT")" == "1234"
test "$(_read_py "$file" "URL")" == "https://yunohost.org"
test "$(ynh_read_var_in_file "$file" "URL")" == "https://yunohost.org"
test "$(ynh_read_var_in_file "$file" "ldap_base")" == "ou=users,dc=yunohost,dc=org"
! _read_py "$file" "NONEXISTENT"
test "$(ynh_read_var_in_file "$file" "NONEXISTENT")" == "YNH_NULL"
! _read_py "$file" "ENABLE"
test "$(ynh_read_var_in_file "$file" "ENABLE")" == "YNH_NULL"
}
ynhtest_config_write_py() {
local dummy_dir="$(mktemp -d -p $VAR_WWW)"
file="$dummy_dir/dummy.py"
cat << EOF > $dummy_dir/dummy.py
# Some comment
FOO = None
ENABLED = False
# TITLE = "Old title"
TITLE = "Lorem Ipsum"
THEME = "colib'ris"
EMAIL = "root@example.com"
PORT = 1234
URL = 'https://yunohost.org'
DICT = {}
DICT['ldap_base'] = "ou=users,dc=yunohost,dc=org"
EOF
#ynh_write_var_in_file "$file" "FOO" "bar"
#test "$(_read_py "$file" "FOO")" == "bar" # FIXME FIXME FIXME
#test "$(ynh_read_var_in_file "$file" "FOO")" == "bar"
ynh_write_var_in_file "$file" "ENABLED" "True"
test "$(_read_py "$file" "ENABLED")" == "True"
test "$(ynh_read_var_in_file "$file" "ENABLED")" == "True"
ynh_write_var_in_file "$file" "TITLE" "Foo Bar"
test "$(_read_py "$file" "TITLE")" == "Foo Bar"
test "$(ynh_read_var_in_file "$file" "TITLE")" == "Foo Bar"
ynh_write_var_in_file "$file" "THEME" "super-awesome-theme"
test "$(_read_py "$file" "THEME")" == "super-awesome-theme"
test "$(ynh_read_var_in_file "$file" "THEME")" == "super-awesome-theme"
ynh_write_var_in_file "$file" "EMAIL" "sam@domain.tld"
test "$(_read_py "$file" "EMAIL")" == "sam@domain.tld"
test "$(ynh_read_var_in_file "$file" "EMAIL")" == "sam@domain.tld"
ynh_write_var_in_file "$file" "PORT" "5678"
test "$(_read_py "$file" "PORT")" == "5678"
test "$(ynh_read_var_in_file "$file" "PORT")" == "5678"
ynh_write_var_in_file "$file" "URL" "https://domain.tld/foobar"
test "$(_read_py "$file" "URL")" == "https://domain.tld/foobar"
test "$(ynh_read_var_in_file "$file" "URL")" == "https://domain.tld/foobar"
ynh_write_var_in_file "$file" "ldap_base" "ou=users,dc=yunohost,dc=org"
test "$(ynh_read_var_in_file "$file" "ldap_base")" == "ou=users,dc=yunohost,dc=org"
ynh_write_var_in_file "$file" "NONEXISTENT" "foobar"
! _read_py "$file" "NONEXISTENT"
test "$(ynh_read_var_in_file "$file" "NONEXISTENT")" == "YNH_NULL"
ynh_write_var_in_file "$file" "ENABLE" "foobar"
! _read_py "$file" "ENABLE"
test "$(ynh_read_var_in_file "$file" "ENABLE")" == "YNH_NULL"
}
###############
# _ _ #
# (_) (_) #
# _ _ __ _ #
# | | '_ \| | #
# | | | | | | #
# |_|_| |_|_| #
# #
###############
_read_ini() {
local file="$1"
local key="$2"
python3 -c "import configparser; c = configparser.ConfigParser(); c.read('$file'); print(c['main']['$key'])"
}
ynhtest_config_read_ini() {
local dummy_dir="$(mktemp -d -p $VAR_WWW)"
file="$dummy_dir/dummy.yml"
cat << EOF > $file
# Some comment
; Another comment
[main]
foo = null
enabled = False
# title = Old title
title = Lorem Ipsum
theme = colib'ris
email = root@example.com
port = 1234
url = https://yunohost.org
[dict]
ldap_base = ou=users,dc=yunohost,dc=org
EOF
test "$(_read_ini "$file" "foo")" == "null"
test "$(ynh_read_var_in_file "$file" "foo")" == "null"
test "$(_read_ini "$file" "enabled")" == "False"
test "$(ynh_read_var_in_file "$file" "enabled")" == "False"
test "$(_read_ini "$file" "title")" == "Lorem Ipsum"
test "$(ynh_read_var_in_file "$file" "title")" == "Lorem Ipsum"
test "$(_read_ini "$file" "theme")" == "colib'ris"
test "$(ynh_read_var_in_file "$file" "theme")" == "colib'ris"
test "$(_read_ini "$file" "email")" == "root@example.com"
test "$(ynh_read_var_in_file "$file" "email")" == "root@example.com"
test "$(_read_ini "$file" "port")" == "1234"
test "$(ynh_read_var_in_file "$file" "port")" == "1234"
test "$(_read_ini "$file" "url")" == "https://yunohost.org"
test "$(ynh_read_var_in_file "$file" "url")" == "https://yunohost.org"
test "$(ynh_read_var_in_file "$file" "ldap_base")" == "ou=users,dc=yunohost,dc=org"
! _read_ini "$file" "nonexistent"
test "$(ynh_read_var_in_file "$file" "nonexistent")" == "YNH_NULL"
! _read_ini "$file" "enable"
test "$(ynh_read_var_in_file "$file" "enable")" == "YNH_NULL"
}
ynhtest_config_write_ini() {
local dummy_dir="$(mktemp -d -p $VAR_WWW)"
file="$dummy_dir/dummy.ini"
cat << EOF > $file
# Some comment
; Another comment
[main]
foo = null
enabled = False
# title = Old title
title = Lorem Ipsum
theme = colib'ris
email = root@example.com
port = 1234
url = https://yunohost.org
[dict]
ldap_base = ou=users,dc=yunohost,dc=org
EOF
ynh_write_var_in_file "$file" "foo" "bar"
test "$(_read_ini "$file" "foo")" == "bar"
test "$(ynh_read_var_in_file "$file" "foo")" == "bar"
ynh_write_var_in_file "$file" "enabled" "True"
test "$(_read_ini "$file" "enabled")" == "True"
test "$(ynh_read_var_in_file "$file" "enabled")" == "True"
ynh_write_var_in_file "$file" "title" "Foo Bar"
test "$(_read_ini "$file" "title")" == "Foo Bar"
test "$(ynh_read_var_in_file "$file" "title")" == "Foo Bar"
ynh_write_var_in_file "$file" "theme" "super-awesome-theme"
test "$(_read_ini "$file" "theme")" == "super-awesome-theme"
test "$(ynh_read_var_in_file "$file" "theme")" == "super-awesome-theme"
ynh_write_var_in_file "$file" "email" "sam@domain.tld"
test "$(_read_ini "$file" "email")" == "sam@domain.tld"
test "$(ynh_read_var_in_file "$file" "email")" == "sam@domain.tld"
ynh_write_var_in_file "$file" "port" "5678"
test "$(_read_ini "$file" "port")" == "5678"
test "$(ynh_read_var_in_file "$file" "port")" == "5678"
ynh_write_var_in_file "$file" "url" "https://domain.tld/foobar"
test "$(_read_ini "$file" "url")" == "https://domain.tld/foobar"
test "$(ynh_read_var_in_file "$file" "url")" == "https://domain.tld/foobar"
ynh_write_var_in_file "$file" "ldap_base" "ou=users,dc=yunohost,dc=org"
test "$(ynh_read_var_in_file "$file" "ldap_base")" == "ou=users,dc=yunohost,dc=org"
ynh_write_var_in_file "$file" "nonexistent" "foobar"
! _read_ini "$file" "nonexistent"
test "$(ynh_read_var_in_file "$file" "nonexistent")" == "YNH_NULL"
ynh_write_var_in_file "$file" "enable" "foobar"
! _read_ini "$file" "enable"
test "$(ynh_read_var_in_file "$file" "enable")" == "YNH_NULL"
}
#############################
# _ #
# | | #
# _ _ __ _ _ __ ___ | | #
# | | | |/ _` | '_ ` _ \| | #
# | |_| | (_| | | | | | | | #
# \__, |\__,_|_| |_| |_|_| #
# __/ | #
# |___/ #
# #
#############################
_read_yaml() {
local file="$1"
local key="$2"
python3 -c "import yaml; print(yaml.safe_load(open('$file'))['$key'])"
}
ynhtest_config_read_yaml() {
local dummy_dir="$(mktemp -d -p $VAR_WWW)"
file="$dummy_dir/dummy.yml"
cat << EOF > $file
# Some comment
foo:
enabled: false
# title: old title
title: Lorem Ipsum
theme: colib'ris
email: root@example.com
port: 1234
url: https://yunohost.org
dict:
ldap_base: ou=users,dc=yunohost,dc=org
EOF
test "$(_read_yaml "$file" "foo")" == "None"
test "$(ynh_read_var_in_file "$file" "foo")" == ""
test "$(_read_yaml "$file" "enabled")" == "False"
test "$(ynh_read_var_in_file "$file" "enabled")" == "false"
test "$(_read_yaml "$file" "title")" == "Lorem Ipsum"
test "$(ynh_read_var_in_file "$file" "title")" == "Lorem Ipsum"
test "$(_read_yaml "$file" "theme")" == "colib'ris"
test "$(ynh_read_var_in_file "$file" "theme")" == "colib'ris"
test "$(_read_yaml "$file" "email")" == "root@example.com"
test "$(ynh_read_var_in_file "$file" "email")" == "root@example.com"
test "$(_read_yaml "$file" "port")" == "1234"
test "$(ynh_read_var_in_file "$file" "port")" == "1234"
test "$(_read_yaml "$file" "url")" == "https://yunohost.org"
test "$(ynh_read_var_in_file "$file" "url")" == "https://yunohost.org"
test "$(ynh_read_var_in_file "$file" "ldap_base")" == "ou=users,dc=yunohost,dc=org"
! _read_yaml "$file" "nonexistent"
test "$(ynh_read_var_in_file "$file" "nonexistent")" == "YNH_NULL"
! _read_yaml "$file" "enable"
test "$(ynh_read_var_in_file "$file" "enable")" == "YNH_NULL"
}
ynhtest_config_write_yaml() {
local dummy_dir="$(mktemp -d -p $VAR_WWW)"
file="$dummy_dir/dummy.yml"
cat << EOF > $file
# Some comment
foo:
enabled: false
# title: old title
title: Lorem Ipsum
theme: colib'ris
email: root@example.com
port: 1234
url: https://yunohost.org
dict:
ldap_base: ou=users,dc=yunohost,dc=org
EOF
#ynh_write_var_in_file "$file" "foo" "bar"
# cat $dummy_dir/dummy.yml # to debug
#! test "$(_read_yaml "$file" "foo")" == "bar" # FIXME FIXME FIXME : writing broke the yaml syntax... "foo:bar" (no space aftr :)
#test "$(ynh_read_var_in_file "$file" "foo")" == "bar"
ynh_write_var_in_file "$file" "enabled" "true"
test "$(_read_yaml "$file" "enabled")" == "True"
test "$(ynh_read_var_in_file "$file" "enabled")" == "true"
ynh_write_var_in_file "$file" "title" "Foo Bar"
test "$(_read_yaml "$file" "title")" == "Foo Bar"
test "$(ynh_read_var_in_file "$file" "title")" == "Foo Bar"
ynh_write_var_in_file "$file" "theme" "super-awesome-theme"
test "$(_read_yaml "$file" "theme")" == "super-awesome-theme"
test "$(ynh_read_var_in_file "$file" "theme")" == "super-awesome-theme"
ynh_write_var_in_file "$file" "email" "sam@domain.tld"
test "$(_read_yaml "$file" "email")" == "sam@domain.tld"
test "$(ynh_read_var_in_file "$file" "email")" == "sam@domain.tld"
ynh_write_var_in_file "$file" "port" "5678"
test "$(_read_yaml "$file" "port")" == "5678"
test "$(ynh_read_var_in_file "$file" "port")" == "5678"
ynh_write_var_in_file "$file" "url" "https://domain.tld/foobar"
test "$(_read_yaml "$file" "url")" == "https://domain.tld/foobar"
test "$(ynh_read_var_in_file "$file" "url")" == "https://domain.tld/foobar"
ynh_write_var_in_file "$file" "ldap_base" "ou=foobar,dc=domain,dc=tld"
test "$(ynh_read_var_in_file "$file" "ldap_base")" == "ou=foobar,dc=domain,dc=tld"
ynh_write_var_in_file "$file" "nonexistent" "foobar"
test "$(ynh_read_var_in_file "$file" "nonexistent")" == "YNH_NULL"
ynh_write_var_in_file "$file" "enable" "foobar"
test "$(ynh_read_var_in_file "$file" "enable")" == "YNH_NULL"
test "$(ynh_read_var_in_file "$file" "enabled")" == "true"
}
#########################
# _ #
# (_) #
# _ ___ ___ _ __ #
# | / __|/ _ \| '_ \ #
# | \__ \ (_) | | | | #
# | |___/\___/|_| |_| #
# _/ | #
# |__/ #
# #
#########################
_read_json() {
local file="$1"
local key="$2"
python3 -c "import json; print(json.load(open('$file'))['$key'])"
}
ynhtest_config_read_json() {
local dummy_dir="$(mktemp -d -p $VAR_WWW)"
file="$dummy_dir/dummy.json"
cat << EOF > $file
{
"foo": null,
"enabled": false,
"title": "Lorem Ipsum",
"theme": "colib'ris",
"email": "root@example.com",
"port": 1234,
"url": "https://yunohost.org",
"dict": {
"ldap_base": "ou=users,dc=yunohost,dc=org"
}
}
EOF
test "$(_read_json "$file" "foo")" == "None"
test "$(ynh_read_var_in_file "$file" "foo")" == "null," # FIXME FIXME FIXME trailing ,
test "$(_read_json "$file" "enabled")" == "False"
test "$(ynh_read_var_in_file "$file" "enabled")" == "false," # FIXME FIXME FIXME trailing ,
test "$(_read_json "$file" "title")" == "Lorem Ipsum"
test "$(ynh_read_var_in_file "$file" "title")" == "Lorem Ipsum"
test "$(_read_json "$file" "theme")" == "colib'ris"
test "$(ynh_read_var_in_file "$file" "theme")" == "colib'ris"
test "$(_read_json "$file" "email")" == "root@example.com"
test "$(ynh_read_var_in_file "$file" "email")" == "root@example.com"
test "$(_read_json "$file" "port")" == "1234"
test "$(ynh_read_var_in_file "$file" "port")" == "1234," # FIXME FIXME FIXME trailing ,
test "$(_read_json "$file" "url")" == "https://yunohost.org"
test "$(ynh_read_var_in_file "$file" "url")" == "https://yunohost.org"
test "$(ynh_read_var_in_file "$file" "ldap_base")" == "ou=users,dc=yunohost,dc=org"
! _read_json "$file" "nonexistent"
test "$(ynh_read_var_in_file "$file" "nonexistent")" == "YNH_NULL"
! _read_json "$file" "enable"
test "$(ynh_read_var_in_file "$file" "enable")" == "YNH_NULL"
}
ynhtest_config_write_json() {
local dummy_dir="$(mktemp -d -p $VAR_WWW)"
file="$dummy_dir/dummy.json"
cat << EOF > $file
{
"foo": null,
"enabled": false,
"title": "Lorem Ipsum",
"theme": "colib'ris",
"email": "root@example.com",
"port": 1234,
"url": "https://yunohost.org",
"dict": {
"ldap_base": "ou=users,dc=yunohost,dc=org"
}
}
EOF
#ynh_write_var_in_file "$file" "foo" "bar"
#cat $file
#test "$(_read_json "$file" "foo")" == "bar" # FIXME FIXME FIXME
#test "$(ynh_read_var_in_file "$file" "foo")" == "bar"
#ynh_write_var_in_file "$file" "enabled" "true"
#test "$(_read_json "$file" "enabled")" == "True" # FIXME FIXME FIXME
#test "$(ynh_read_var_in_file "$file" "enabled")" == "true"
ynh_write_var_in_file "$file" "title" "Foo Bar"
cat $file
test "$(_read_json "$file" "title")" == "Foo Bar"
test "$(ynh_read_var_in_file "$file" "title")" == "Foo Bar"
ynh_write_var_in_file "$file" "theme" "super-awesome-theme"
cat $file
test "$(_read_json "$file" "theme")" == "super-awesome-theme"
test "$(ynh_read_var_in_file "$file" "theme")" == "super-awesome-theme"
ynh_write_var_in_file "$file" "email" "sam@domain.tld"
cat $file
test "$(_read_json "$file" "email")" == "sam@domain.tld"
test "$(ynh_read_var_in_file "$file" "email")" == "sam@domain.tld"
#ynh_write_var_in_file "$file" "port" "5678"
#cat $file
#test "$(_read_json "$file" "port")" == "5678" # FIXME FIXME FIXME
#test "$(ynh_read_var_in_file "$file" "port")" == "5678"
ynh_write_var_in_file "$file" "url" "https://domain.tld/foobar"
test "$(_read_json "$file" "url")" == "https://domain.tld/foobar"
test "$(ynh_read_var_in_file "$file" "url")" == "https://domain.tld/foobar"
ynh_write_var_in_file "$file" "ldap_base" "ou=foobar,dc=domain,dc=tld"
test "$(ynh_read_var_in_file "$file" "ldap_base")" == "ou=foobar,dc=domain,dc=tld"
ynh_write_var_in_file "$file" "nonexistent" "foobar"
test "$(ynh_read_var_in_file "$file" "nonexistent")" == "YNH_NULL"
ynh_write_var_in_file "$file" "enable" "foobar"
test "$(ynh_read_var_in_file "$file" "enable")" == "YNH_NULL"
#test "$(ynh_read_var_in_file "$file" "enabled")" == "true" # FIXME
}
#######################
# _ #
# | | #
# _ __ | |__ _ __ #
# | '_ \| '_ \| '_ \ #
# | |_) | | | | |_) | #
# | .__/|_| |_| .__/ #
# | | | | #
# |_| |_| #
# #
#######################
_read_php() {
local file="$1"
local key="$2"
php -r "include '$file'; echo var_export(\$$key);" | sed "s/^'//g" | sed "s/'$//g"
}
ynhtest_config_read_php() {
local dummy_dir="$(mktemp -d -p $VAR_WWW)"
file="$dummy_dir/dummy.php"
cat << EOF > $file
<?php
// Some comment
\$foo = NULL;
\$enabled = false;
// \$title = "old title";
\$title = "Lorem Ipsum";
\$theme = "colib'ris";
\$email = "root@example.com";
\$port = 1234;
\$url = "https://yunohost.org";
\$dict = [
'ldap_base' => "ou=users,dc=yunohost,dc=org",
];
?>
EOF
test "$(_read_php "$file" "foo")" == "NULL"
test "$(ynh_read_var_in_file "$file" "foo")" == "NULL;" # FIXME FIXME FIXME trailing ;
test "$(_read_php "$file" "enabled")" == "false"
test "$(ynh_read_var_in_file "$file" "enabled")" == "false;" # FIXME FIXME FIXME trailing ;
test "$(_read_php "$file" "title")" == "Lorem Ipsum"
test "$(ynh_read_var_in_file "$file" "title")" == "Lorem Ipsum"
test "$(_read_php "$file" "theme")" == "colib\\'ris"
test "$(ynh_read_var_in_file "$file" "theme")" == "colib'ris"
test "$(_read_php "$file" "email")" == "root@example.com"
test "$(ynh_read_var_in_file "$file" "email")" == "root@example.com"
test "$(_read_php "$file" "port")" == "1234"
test "$(ynh_read_var_in_file "$file" "port")" == "1234;" # FIXME FIXME FIXME trailing ;
test "$(_read_php "$file" "url")" == "https://yunohost.org"
test "$(ynh_read_var_in_file "$file" "url")" == "https://yunohost.org"
test "$(ynh_read_var_in_file "$file" "ldap_base")" == "ou=users,dc=yunohost,dc=org"
! _read_php "$file" "nonexistent"
test "$(ynh_read_var_in_file "$file" "nonexistent")" == "YNH_NULL"
! _read_php "$file" "enable"
test "$(ynh_read_var_in_file "$file" "enable")" == "YNH_NULL"
}
ynhtest_config_write_php() {
local dummy_dir="$(mktemp -d -p $VAR_WWW)"
file="$dummy_dir/dummy.php"
cat << EOF > $file
<?php
// Some comment
\$foo = NULL;
\$enabled = false;
// \$title = "old title";
\$title = "Lorem Ipsum";
\$theme = "colib'ris";
\$email = "root@example.com";
\$port = 1234;
\$url = "https://yunohost.org";
\$dict = [
'ldap_base' => "ou=users,dc=yunohost,dc=org",
];
?>
EOF
#ynh_write_var_in_file "$file" "foo" "bar"
#cat $file
#test "$(_read_php "$file" "foo")" == "bar"
#test "$(ynh_read_var_in_file "$file" "foo")" == "bar" # FIXME FIXME FIXME
#ynh_write_var_in_file "$file" "enabled" "true"
#cat $file
#test "$(_read_php "$file" "enabled")" == "true"
#test "$(ynh_read_var_in_file "$file" "enabled")" == "true" # FIXME FIXME FIXME
ynh_write_var_in_file "$file" "title" "Foo Bar"
cat $file
test "$(_read_php "$file" "title")" == "Foo Bar"
test "$(ynh_read_var_in_file "$file" "title")" == "Foo Bar"
ynh_write_var_in_file "$file" "theme" "super-awesome-theme"
cat $file
test "$(_read_php "$file" "theme")" == "super-awesome-theme"
test "$(ynh_read_var_in_file "$file" "theme")" == "super-awesome-theme"
ynh_write_var_in_file "$file" "email" "sam@domain.tld"
cat $file
test "$(_read_php "$file" "email")" == "sam@domain.tld"
test "$(ynh_read_var_in_file "$file" "email")" == "sam@domain.tld"
#ynh_write_var_in_file "$file" "port" "5678"
#cat $file
#test "$(_read_php "$file" "port")" == "5678" # FIXME FIXME FIXME
#test "$(ynh_read_var_in_file "$file" "port")" == "5678"
ynh_write_var_in_file "$file" "url" "https://domain.tld/foobar"
test "$(_read_php "$file" "url")" == "https://domain.tld/foobar"
test "$(ynh_read_var_in_file "$file" "url")" == "https://domain.tld/foobar"
ynh_write_var_in_file "$file" "ldap_base" "ou=foobar,dc=domain,dc=tld"
test "$(ynh_read_var_in_file "$file" "ldap_base")" == "ou=foobar,dc=domain,dc=tld"
ynh_write_var_in_file "$file" "nonexistent" "foobar"
test "$(ynh_read_var_in_file "$file" "nonexistent")" == "YNH_NULL"
ynh_write_var_in_file "$file" "enable" "foobar"
test "$(ynh_read_var_in_file "$file" "enable")" == "YNH_NULL"
#test "$(ynh_read_var_in_file "$file" "enabled")" == "true" # FIXME
}