diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 23a0075de..eb34de38e 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,142 +1,22 @@ stages: - - postinstall + - build + - install - tests - lint + - doc -######################################## -# POSTINSTALL -######################################## +default: + tags: + - yunohost-ci + # All jobs are interruptible by default + interruptible: true -postinstall: - image: before-postinstall - stage: postinstall - script: - - apt install --no-install-recommends -y $(cat debian/control | grep "^Depends" -A50 | grep "Recommends:" -B50 | grep "^ *," | grep -o -P "[\w\-]{3,}") - - yunohost tools postinstall -d domain.tld -p the_password --ignore-dyndns +variables: + YNH_BUILD_DIR: "ynh-build" -######################################## -# TESTS -######################################## - -.test-stage: - image: after-postinstall - stage: tests - before_script: - - apt-get install python-pip -y - - mkdir -p .pip - - pip install -U pip - - hash -d pip - - pip --cache-dir=.pip install pytest pytest-sugar pytest-mock requests-mock mock - - export PYTEST_ADDOPTS="--color=yes" - cache: - paths: - - .pip - - src/yunohost/tests/apps - key: "$CI_JOB_STAGE-$CI_COMMIT_REF_SLUG" - -root-tests: - extends: .test-stage - script: - - py.test tests - -test-apps: - extends: .test-stage - script: - - cd src/yunohost - - py.test tests/test_apps.py - -test-appscatalog: - extends: .test-stage - script: - - cd src/yunohost - - py.test tests/test_appscatalog.py - -test-appurl: - extends: .test-stage - script: - - cd src/yunohost - - py.test tests/test_appurl.py - -test-apps-arguments-parsing: - extends: .test-stage - script: - - cd src/yunohost - - py.test tests/test_apps_arguments_parsing.py - -test-backuprestore: - extends: .test-stage - script: - - cd src/yunohost - - py.test tests/test_backuprestore.py - -test-changeurl: - extends: .test-stage - script: - - cd src/yunohost - - py.test tests/test_changeurl.py - -test-permission: - extends: .test-stage - script: - - cd src/yunohost - - py.test tests/test_permission.py - -test-settings: - extends: .test-stage - script: - - cd src/yunohost - - py.test tests/test_settings.py - -test-user-group: - extends: .test-stage - script: - - cd src/yunohost - - py.test tests/test_user-group.py - -test-regenconf: - extends: .test-stage - script: - - cd src/yunohost - - py.test tests/test_regenconf.py - -test-service: - extends: .test-stage - script: - - cd src/yunohost - - py.test tests/test_service.py - -######################################## -# LINTER -######################################## - -.lint-stage: - image: before-postinstall - stage: lint - before_script: - - apt-get install python-pip -y - - mkdir -p .pip - - pip install -U pip - - hash -d pip - - pip --cache-dir=.pip install tox - cache: - paths: - - .pip - - .tox - key: "$CI_JOB_STAGE-$CI_COMMIT_REF_SLUG" - -lint: - extends: .lint-stage - allow_failure: true - script: - - tox -e lint - -invalidcode: - extends: .lint-stage - script: - - tox -e invalidcode - -# Disabled, waiting for buster -#format-check: -# extends: .lint-stage -# script: -# - black --check --diff +include: + - local: .gitlab/ci/build.gitlab-ci.yml + - local: .gitlab/ci/install.gitlab-ci.yml + - local: .gitlab/ci/test.gitlab-ci.yml + - local: .gitlab/ci/lint.gitlab-ci.yml + - local: .gitlab/ci/doc.gitlab-ci.yml diff --git a/.gitlab/ci/build.gitlab-ci.yml b/.gitlab/ci/build.gitlab-ci.yml new file mode 100644 index 000000000..67232ba1f --- /dev/null +++ b/.gitlab/ci/build.gitlab-ci.yml @@ -0,0 +1,52 @@ +.build-stage: + stage: build + image: "before-install" + variables: + YNH_SOURCE: "https://github.com/yunohost" + before_script: + - mkdir -p $YNH_BUILD_DIR + artifacts: + paths: + - $YNH_BUILD_DIR/*.deb + +.build_script: &build_script + - cd $YNH_BUILD_DIR/$PACKAGE + - VERSION=$(dpkg-parsechangelog -S Version 2>/dev/null) + - VERSION_NIGHTLY="${VERSION}+$(date +%Y%m%d%H%M)" + - dch --package "${PACKAGE}" --force-bad-version -v "${VERSION_NIGHTLY}" -D "unstable" --force-distribution "Daily build." + - debuild --no-lintian -us -uc + +######################################## +# BUILD DEB +######################################## + +build-yunohost: + extends: .build-stage + variables: + PACKAGE: "yunohost" + script: + - git ls-files | xargs tar -czf archive.tar.gz + - mkdir -p $YNH_BUILD_DIR/$PACKAGE + - cat archive.tar.gz | tar -xz -C $YNH_BUILD_DIR/$PACKAGE + - rm archive.tar.gz + - DEBIAN_FRONTEND=noninteractive apt --assume-yes -o Dpkg::Options::="--force-confold" build-dep $(pwd)/$YNH_BUILD_DIR/$PACKAGE + - *build_script + + +build-ssowat: + extends: .build-stage + variables: + PACKAGE: "ssowat" + script: + - git clone $YNH_SOURCE/$PACKAGE -b $CI_COMMIT_REF_NAME $YNH_BUILD_DIR/$PACKAGE || git clone $YNH_SOURCE/$PACKAGE $YNH_BUILD_DIR/$PACKAGE + - DEBIAN_FRONTEND=noninteractive apt --assume-yes -o Dpkg::Options::="--force-confold" build-dep $(pwd)/$YNH_BUILD_DIR/$PACKAGE + - *build_script + +build-moulinette: + extends: .build-stage + variables: + PACKAGE: "moulinette" + script: + - git clone $YNH_SOURCE/$PACKAGE -b $CI_COMMIT_REF_NAME $YNH_BUILD_DIR/$PACKAGE || git clone $YNH_SOURCE/$PACKAGE $YNH_BUILD_DIR/$PACKAGE + - DEBIAN_FRONTEND=noninteractive apt --assume-yes -o Dpkg::Options::="--force-confold" build-dep $(pwd)/$YNH_BUILD_DIR/$PACKAGE + - *build_script diff --git a/.gitlab/ci/doc.gitlab-ci.yml b/.gitlab/ci/doc.gitlab-ci.yml new file mode 100644 index 000000000..b246cc238 --- /dev/null +++ b/.gitlab/ci/doc.gitlab-ci.yml @@ -0,0 +1,14 @@ +######################################## +# DOC +######################################## + +generate-helpers-doc: + stage: doc + image: "before-install" + needs: [] + script: + - cd doc + - python generate_helper_doc.py + artifacts: + paths: + - doc/helpers.html diff --git a/.gitlab/ci/install.gitlab-ci.yml b/.gitlab/ci/install.gitlab-ci.yml new file mode 100644 index 000000000..20a191757 --- /dev/null +++ b/.gitlab/ci/install.gitlab-ci.yml @@ -0,0 +1,29 @@ +.install-stage: + stage: install + needs: + - job: build-yunohost + artifacts: true + - job: build-ssowat + artifacts: true + - job: build-moulinette + artifacts: true + +######################################## +# INSTALL DEB +######################################## + +upgrade: + extends: .install-stage + image: "after-install" + script: + - apt update + - DEBIAN_FRONTEND=noninteractive SUDO_FORCE_REMOVE=yes apt --assume-yes -o Dpkg::Options::="--force-confold" --allow-downgrades install ./$YNH_BUILD_DIR/*.deb + + +install-postinstall: + extends: .install-stage + image: "before-install" + script: + - apt update + - DEBIAN_FRONTEND=noninteractive SUDO_FORCE_REMOVE=yes apt --assume-yes -o Dpkg::Options::="--force-confold" --allow-downgrades install ./$YNH_BUILD_DIR/*.deb + - yunohost tools postinstall -d domain.tld -p the_password --ignore-dyndns diff --git a/.gitlab/ci/lint.gitlab-ci.yml b/.gitlab/ci/lint.gitlab-ci.yml new file mode 100644 index 000000000..c6967d5a5 --- /dev/null +++ b/.gitlab/ci/lint.gitlab-ci.yml @@ -0,0 +1,24 @@ +######################################## +# LINTER +######################################## + +lint: + stage: lint + image: "before-install" + needs: [] + allow_failure: true + script: + - tox -e lint + +invalidcode: + stage: lint + image: "before-install" + needs: [] + script: + - tox -e invalidcode + +# Disabled, waiting for buster +#format-check: +# extends: .lint-stage +# script: +# - black --check --diff diff --git a/.gitlab/ci/test.gitlab-ci.yml b/.gitlab/ci/test.gitlab-ci.yml new file mode 100644 index 000000000..06e9eeed6 --- /dev/null +++ b/.gitlab/ci/test.gitlab-ci.yml @@ -0,0 +1,120 @@ +.install_debs: &install_debs + - apt update + - DEBIAN_FRONTEND=noninteractive SUDO_FORCE_REMOVE=yes apt --assume-yes -o Dpkg::Options::="--force-confold" --allow-downgrades install ./$YNH_BUILD_DIR/*.deb + +.test-stage: + stage: tests + image: "after-install" + variables: + PYTEST_ADDOPTS: "--color=yes" + before_script: + - *install_debs + cache: + paths: + - src/yunohost/tests/apps + key: "$CI_JOB_STAGE-$CI_COMMIT_REF_SLUG" + needs: + - job: build-yunohost + artifacts: true + - job: build-ssowat + artifacts: true + - job: build-moulinette + artifacts: true + - job: upgrade + + +######################################## +# TESTS +######################################## + +full-tests: + stage: tests + image: "before-install" + variables: + PYTEST_ADDOPTS: "--color=yes" + before_script: + - *install_debs + - yunohost tools postinstall -d domain.tld -p the_password --ignore-dyndns + script: + - pytest --cov=yunohost tests/ src/yunohost/tests/ --junitxml=report.xml + needs: + - job: build-yunohost + artifacts: true + - job: build-ssowat + artifacts: true + - job: build-moulinette + artifacts: true + artifacts: + reports: + junit: report.xml + +root-tests: + extends: .test-stage + script: + - py.test tests + +test-apps: + extends: .test-stage + script: + - cd src/yunohost + - py.test tests/test_apps.py + +test-appscatalog: + extends: .test-stage + script: + - cd src/yunohost + - py.test tests/test_appscatalog.py + +test-appurl: + extends: .test-stage + script: + - cd src/yunohost + - py.test tests/test_appurl.py + +test-apps-arguments-parsing: + extends: .test-stage + script: + - cd src/yunohost + - py.test tests/test_apps_arguments_parsing.py + +test-backuprestore: + extends: .test-stage + script: + - cd src/yunohost + - py.test tests/test_backuprestore.py + +test-changeurl: + extends: .test-stage + script: + - cd src/yunohost + - py.test tests/test_changeurl.py + +test-permission: + extends: .test-stage + script: + - cd src/yunohost + - py.test tests/test_permission.py + +test-settings: + extends: .test-stage + script: + - cd src/yunohost + - py.test tests/test_settings.py + +test-user-group: + extends: .test-stage + script: + - cd src/yunohost + - py.test tests/test_user-group.py + +test-regenconf: + extends: .test-stage + script: + - cd src/yunohost + - py.test tests/test_regenconf.py + +test-service: + extends: .test-stage + script: + - cd src/yunohost + - py.test tests/test_service.py diff --git a/data/helpers.d/apt b/data/helpers.d/apt index c6621d814..31f7d4ffe 100644 --- a/data/helpers.d/apt +++ b/data/helpers.d/apt @@ -193,37 +193,17 @@ ynh_package_install_from_equivs () { LC_ALL=C equivs-build ./control 1> /dev/null dpkg --force-depends --install "./${pkgname}_${pkgversion}_all.deb" 2>&1) - # Let's try to see if install will work using dry-run. It it fails, - # it could be because the pinning of sury is blocking some package install - # c.f. for example: https://github.com/YunoHost/issues/issues/1563#issuecomment-623406509 - # ... In that case, we use an ugly hack were we'll use a tweaked - # preferences.d directory with looser contrains for sury... - if ! ynh_package_install --fix-broken --dry-run >/dev/null 2>&1 && [ -e /etc/apt/preferences.d/extra_php_version ] - then - cp -r /etc/apt/preferences.d/ /etc/apt/preferences.d.tmp/ - sed 's/^Pin-Priority: .*/Pin-Priority: 600/g' -i /etc/apt/preferences.d.tmp/extra_php_version - local apt_tweaks='--option Dir::Etc::preferencesparts=preferences.d.tmp' - # Try a dry-run again to see if that fixes the issue ... - # If it did not, then that's probably not related to sury. - ynh_package_install $apt_tweaks --fix-broken --dry-run >/dev/null 2>&1 || apt_tweaks="" - else - local apt_tweaks="" - fi - - # Try to install for real - ynh_package_install $apt_tweaks --fix-broken || \ + ynh_package_install --fix-broken || \ { # If the installation failed # (the following is ran inside { } to not start a subshell otherwise ynh_die wouldnt exit the original process) - rm --recursive --force /etc/apt/preferences.d.tmp/ # Get the list of dependencies from the deb local dependencies="$(dpkg --info "$TMPDIR/${pkgname}_${pkgversion}_all.deb" | grep Depends | \ sed 's/^ Depends: //' | sed 's/,//g')" # Fake an install of those dependencies to see the errors # The sed command here is, Print only from '--fix-broken' to the end. - ynh_package_install $apt_tweaks $dependencies --dry-run | sed --quiet '/--fix-broken/,$p' >&2 + ynh_package_install $dependencies --dry-run | sed --quiet '/--fix-broken/,$p' >&2 ynh_die --message="Unable to install dependencies"; } [[ -n "$TMPDIR" ]] && rm --recursive --force $TMPDIR # Remove the temp dir. - rm --recursive --force /etc/apt/preferences.d.tmp/ # check if the package is actually installed ynh_package_is_installed "$pkgname" @@ -287,8 +267,10 @@ ynh_install_app_dependencies () { ynh_install_extra_repo --repo="https://packages.sury.org/php/ $(ynh_get_debian_release) main" --key="https://packages.sury.org/php/apt.gpg" --name=extra_php_version # Pin this sury repository to prevent sury of doing shit - ynh_pin_repo --package="*" --pin="origin \"packages.sury.org\"" --priority=200 --name=extra_php_version - ynh_pin_repo --package="php${YNH_DEFAULT_PHP_VERSION}*" --pin="origin \"packages.sury.org\"" --priority=600 --name=extra_php_version --append + for package_to_not_upgrade in "php" "php-fpm" "php-mysql" "php-xml" "php-zip" "php-mbstring" "php-ldap" "php-gd" "php-curl" "php-bz2" "php-json" "php-sqlite3" "php-intl" "openssl" "libssl1.1" "libssl-dev" + do + ynh_pin_repo --package="$package_to_not_upgrade" --pin="origin \"packages.sury.org\"" --priority="-1" --name=extra_php_version --append + done fi fi fi diff --git a/data/helpers.d/php b/data/helpers.d/php index 9b9df64f9..5d7b0b1e2 100644 --- a/data/helpers.d/php +++ b/data/helpers.d/php @@ -359,8 +359,10 @@ ynh_install_php () { update-alternatives --set php /usr/bin/php$YNH_DEFAULT_PHP_VERSION # Pin this extra repository after packages are installed to prevent sury of doing shit - ynh_pin_repo --package="*" --pin="origin \"packages.sury.org\"" --priority=200 --name=extra_php_version - ynh_pin_repo --package="php${YNH_DEFAULT_PHP_VERSION}*" --pin="origin \"packages.sury.org\"" --priority=600 --name=extra_php_version --append + for package_to_not_upgrade in "php" "php-fpm" "php-mysql" "php-xml" "php-zip" "php-mbstring" "php-ldap" "php-gd" "php-curl" "php-bz2" "php-json" "php-sqlite3" "php-intl" "openssl" "libssl1.1" "libssl-dev" + do + ynh_pin_repo --package="$package_to_not_upgrade" --pin="origin \"packages.sury.org\"" --priority="-1" --name=extra_php_version --append + done # Advertise service in admin panel yunohost service add php${phpversion}-fpm --log "/var/log/php${phpversion}-fpm.log" diff --git a/data/helpers.d/postgresql b/data/helpers.d/postgresql index 78ef4f7ce..cce1f7959 100644 --- a/data/helpers.d/postgresql +++ b/data/helpers.d/postgresql @@ -229,12 +229,14 @@ ynh_psql_setup_db() { # Manage arguments with getopts ynh_handle_getopts_args "$@" - local new_db_pwd=$(ynh_string_random) # Generate a random password - # If $db_pwd is not given, use new_db_pwd instead for db_pwd - db_pwd="${db_pwd:-$new_db_pwd}" - if ! ynh_psql_user_exists --user=$db_user; then + local new_db_pwd=$(ynh_string_random) # Generate a random password + # If $db_pwd is not given, use new_db_pwd instead for db_pwd + db_pwd="${db_pwd:-$new_db_pwd}" + ynh_psql_create_user "$db_user" "$db_pwd" + elif [ -z $db_pwd ]; then + ynh_die --message="The user $db_user exists, please provide his password" fi ynh_psql_create_db "$db_name" "$db_user" # Create the database @@ -293,7 +295,7 @@ ynh_psql_test_if_first_run() { # postgresql could be flagged as active even though the cluster is in # failed state because of how the service is configured..) systemctl is-active postgresql@$PSQL_VERSION-main -q || ynh_systemd_action --service_name=postgresql --action=restart - systemctl is-enabled postgresql -q || systemctl enable postgresql + systemctl is-enabled postgresql -q || systemctl enable postgresql --quiet # If this is the very first time, we define the root password # and configure a few things diff --git a/data/helpers.d/systemd b/data/helpers.d/systemd index 236781f8b..5c5fda852 100644 --- a/data/helpers.d/systemd +++ b/data/helpers.d/systemd @@ -58,7 +58,7 @@ ynh_add_systemd_config () { ynh_store_file_checksum --file="$finalsystemdconf" chown root: "$finalsystemdconf" - systemctl enable $service + systemctl enable $service --quiet systemctl daemon-reload } @@ -81,7 +81,7 @@ ynh_remove_systemd_config () { if [ -e "$finalsystemdconf" ] then ynh_systemd_action --service_name=$service --action=stop - systemctl disable $service + systemctl disable $service --quiet ynh_secure_remove --file="$finalsystemdconf" systemctl daemon-reload fi diff --git a/data/hooks/conf_regen/01-yunohost b/data/hooks/conf_regen/01-yunohost index 4bd763b70..abc6f3780 100755 --- a/data/hooks/conf_regen/01-yunohost +++ b/data/hooks/conf_regen/01-yunohost @@ -55,14 +55,18 @@ do_pre_regen() { fi # add cron job for diagnosis to be ran at 7h and 19h + a random delay between - # 0 and 10min, meant to avoid every instances running their diagnosis at + # 0 and 20min, meant to avoid every instances running their diagnosis at # exactly the same time, which may overload the diagnosis server. mkdir -p $pending_dir/etc/cron.d/ cat > $pending_dir/etc/cron.d/yunohost-diagnosis << EOF SHELL=/bin/bash -0 7,19 * * * root : YunoHost Automatic Diagnosis; sleep \$((RANDOM\\%600)); yunohost diagnosis run --email > /dev/null 2>/dev/null || echo "Running the automatic diagnosis failed miserably" +0 7,19 * * * root : YunoHost Automatic Diagnosis; sleep \$((RANDOM\\%1200)); yunohost diagnosis run --email > /dev/null 2>/dev/null || echo "Running the automatic diagnosis failed miserably" EOF + # legacy stuff to avoid yunohost reporting etckeeper as manually modified + # (this make sure that the hash is null / file is flagged as to-delete) + mkdir -p $pending_dir/etc/etckeeper + touch $pending_dir/etc/etckeeper/etckeeper.conf } do_post_regen() { @@ -98,7 +102,7 @@ with open('services.yml') as f: new_services = yaml.load(f) with open('/etc/yunohost/services.yml') as f: - services = yaml.load(f) + services = yaml.load(f) or {} updated = False diff --git a/data/hooks/conf_regen/34-mysql b/data/hooks/conf_regen/34-mysql index 43f9fdde1..74bafbf71 100755 --- a/data/hooks/conf_regen/34-mysql +++ b/data/hooks/conf_regen/34-mysql @@ -15,6 +15,16 @@ do_pre_regen() { do_post_regen() { regen_conf_files=$1 + # mysql is supposed to be an alias to mariadb... but in some weird case is not + # c.f. https://forum.yunohost.org/t/mysql-ne-fonctionne-pas/11661 + # Playing with enable/disable allows to recreate the proper symlinks. + if [ ! -e /etc/systemd/system/mysql.service ] + then + systemctl disable mysql -q + systemctl disable mariadb -q + systemctl enable mariadb -q + fi + if [ ! -f /etc/yunohost/mysql ]; then # ensure that mysql is running diff --git a/data/hooks/conf_regen/40-glances b/data/hooks/conf_regen/40-glances deleted file mode 100755 index 70b8f4b5a..000000000 --- a/data/hooks/conf_regen/40-glances +++ /dev/null @@ -1,36 +0,0 @@ -#!/bin/bash - -set -e - -do_pre_regen() { - pending_dir=$1 - - cd /usr/share/yunohost/templates/glances - - install -D -m 644 glances.default "${pending_dir}/etc/default/glances" -} - -do_post_regen() { - regen_conf_files=$1 - - [[ -z "$regen_conf_files" ]] \ - || service glances restart -} - -FORCE=${2:-0} -DRY_RUN=${3:-0} - -case "$1" in - pre) - do_pre_regen $4 - ;; - post) - do_post_regen $4 - ;; - *) - echo "hook called with unknown argument \`$1'" >&2 - exit 1 - ;; -esac - -exit 0 diff --git a/data/hooks/diagnosis/12-dnsrecords.py b/data/hooks/diagnosis/12-dnsrecords.py index 560127bb0..91dec7006 100644 --- a/data/hooks/diagnosis/12-dnsrecords.py +++ b/data/hooks/diagnosis/12-dnsrecords.py @@ -12,9 +12,7 @@ from yunohost.utils.network import dig from yunohost.diagnosis import Diagnoser from yunohost.domain import domain_list, _build_dns_conf, _get_maindomain -# We put here domains we know has dyndns provider, but that are not yet -# registered in the public suffix list -PENDING_SUFFIX_LIST = ['ynh.fr', 'netlib.re'] +YNH_DYNDNS_DOMAINS = ['nohost.me', 'noho.st', 'ynh.fr'] class DNSRecordsDiagnoser(Diagnoser): @@ -38,7 +36,7 @@ class DNSRecordsDiagnoser(Diagnoser): psl = PublicSuffixList() domains_from_registrar = [psl.get_public_suffix(domain) for domain in all_domains] domains_from_registrar = [domain for domain in domains_from_registrar if "." in domain] - domains_from_registrar = set(domains_from_registrar) - set(PENDING_SUFFIX_LIST) + domains_from_registrar = set(domains_from_registrar) - set(YNH_DYNDNS_DOMAINS + ["netlib.re"]) for report in self.check_expiration_date(domains_from_registrar): yield report @@ -100,7 +98,13 @@ class DNSRecordsDiagnoser(Diagnoser): summary=summary) if discrepancies: - output["details"] = ["diagnosis_dns_point_to_doc"] + discrepancies + # For ynh-managed domains (nohost.me etc...), tell people to try to "yunohost dyndns update --force" + if any(domain.endswith(ynh_dyndns_domain) for ynh_dyndns_domain in YNH_DYNDNS_DOMAINS): + output["details"] = ["diagnosis_dns_try_dyndns_update_force"] + # Otherwise point to the documentation + else: + output["details"] = ["diagnosis_dns_point_to_doc"] + output["details"] += discrepancies yield output diff --git a/data/hooks/diagnosis/24-mail.py b/data/hooks/diagnosis/24-mail.py index bc159c3b7..a483e676d 100644 --- a/data/hooks/diagnosis/24-mail.py +++ b/data/hooks/diagnosis/24-mail.py @@ -126,7 +126,7 @@ class MailDiagnoser(Diagnoser): query += '.ip6.arpa' # Do the DNS Query - status, value = dig(query, 'PTR') + status, value = dig(query, 'PTR', resolvers="force_external") if status == "nok": yield dict(meta={"test": "mail_fcrdns", "ipversion": ipversion}, data={"ip": ip, "ehlo_domain": self.ehlo_domain}, diff --git a/data/templates/metronome/domain.tpl.cfg.lua b/data/templates/metronome/domain.tpl.cfg.lua index aa2f45e5a..e5e169791 100644 --- a/data/templates/metronome/domain.tpl.cfg.lua +++ b/data/templates/metronome/domain.tpl.cfg.lua @@ -47,6 +47,10 @@ Component "muc.{{ domain }}" "muc" muc_event_rate = 0.5 muc_burst_factor = 10 + room_default_config = { + logging = true, + persistent = true + }; ---Set up a PubSub server Component "pubsub.{{ domain }}" "pubsub" diff --git a/data/templates/nginx/plain/ssowat.conf b/data/templates/nginx/plain/ssowat.conf index c82cd40ea..bd8d5a73a 100644 --- a/data/templates/nginx/plain/ssowat.conf +++ b/data/templates/nginx/plain/ssowat.conf @@ -1,3 +1,3 @@ lua_shared_dict cache 10m; init_by_lua_file /usr/share/ssowat/init.lua; -server_names_hash_bucket_size 64; +server_names_hash_bucket_size 128; diff --git a/debian/changelog b/debian/changelog index 83ae9c3d4..2916f1b8f 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,48 @@ +yunohost (3.8.4.6) stable; urgency=low + + - [fix] Bump server_names_hash_bucket_size to 128 to avoid nginx exploding for stupid reasons (b3db4d92) + - [fix] More sensible strategy for sury pinning (#1006) + - [fix] Stop trying to fetch log categories that are not implemented yet T.T (77bd9ae3) + - [enh] Add logging and persistent as default config for new muc room (#1008) + - [tests] Moar tests for app args parsing (#1004) + + Thanks to all contributors <3 ! (Gabriel, Kay0u, Bram) + + -- Alexandre Aubin Thu, 28 May 2020 00:22:10 +0200 + +yunohost (3.8.4.5) stable; urgency=low + + - [enh] Tell systemctl to stfu about creating symlinks when enabling/disabling services (6637c8a8) + - [enh] Add maindomain in diagnosis email subject (e30e25fa) + - [fix] Webpath should also be normalized for args_list, so that we can get rid of the 'malformed path' check of the CI... (58ce6e5e) + - [fix] Increase time window for auto diagnosis cron to avoid remote diagnosis server overload (dc221495) + - [fix] encoding bullshit (4c600125, 64596bc1) + - [fix] Typo in diagnosis message + fix FR translation report format of bad DNS conf (#1002, b8f8ea14) + - [fix] Flag old etckeeper.conf as 'should not exist' in regenconf (5a3b382f) + - [enh] Detect dyndns-domains managed by yunohost and advice to use yunohost dyndns update --force (8b169f13) + - [enh] Complain if apps savagely edit system configurations during install and upgrade (a23f02db) + - [i18n] Translations updated for Arabic, Catalan, French, German, Italian + - [tests] CI V2 : Rework CI workflow (#991) + + Thanks to all contributors <3 ! (ButterflyOfFire, Kay0u, L. Noferini, rynas, V. Rubiolo, xaloc33, Yasss Gurl) + + -- Alexandre Aubin Tue, 26 May 2020 03:20:39 +0200 + +yunohost (3.8.4.4) stable; urgency=low + + - [fix] Crash when the services file is empty (85f1802) + - [fix] IPv6 detection when using wg-quick (#997) + - [fix] Use a .get() to avoid crash if key doesn't exist (1f1b2338) + - [enh] Don't display the hostname when calling journalctl, this takes horizontal space for nothing (2bcfb5a1) + - [fix] Add --quiet, otherwise getopts is confused by "-- Logs" at the beginning (bdbf1822) + - [mod] We don't need those color codes... and warnings are already warnings... (2a631fa2) + - [fix] psql_setup_db: Do not create a new password if the user already exists (#998) + - [enh] Add an exception if packaging format is not recognized (f0cc6798) + + Thanks to all contributors <3 ! (Aleks, Julien Rabier, Kayou) + + -- Kay0u Fri, 22 May 2020 19:26:05 +0000 + yunohost (3.8.4.3) stable; urgency=low - [fix] Workaround for the sury pinning issues when installing dependencies @@ -18,8 +63,8 @@ yunohost (3.8.4.2) testing; urgency=low - [fix] Diagnosis: Try to not have weird warnings if no diagnosis ran yet... (65c87d55) - [fix] Diagnosis: Change logic of --email to avoid sending empty mail if some issues are found but ignored (4cd4938e) - [enh] Diagnosis/services: Report the service status as warning/unknown if service type is oneshot and status exited (dd09758f, 1cd7ffea) - - [fix] Rework ynh_psql_test_if_first_run ([#993](https://github.com/yunohost/yunohost/pull/993)) - - [tests] Tests for args parsing ([#989](https://github.com/yunohost/yunohost/pull/989), 108a3ca4) + - [fix] Rework ynh_psql_test_if_first_run (#993) + - [tests] Tests for args parsing (#989, 108a3ca4) Thanks to all contributors <3 ! (Bram, Kayou) @@ -1698,7 +1743,7 @@ yunohost (2.5.2) testing; urgency=low Other fixes and improvements: * [enh] remove timeout from cli interface - * [fix] [#662](https://dev.yunohost.org/issues/662): missing 'python-openssl' dependency for Let's Encrypt integration. + * [fix] #662: missing 'python-openssl' dependency for Let's Encrypt integration. * [fix] --no-remove-on-failure for app install should behave as a flag. * [fix] don't remove trailing char if it's not a slash diff --git a/locales/ar.json b/locales/ar.json index 9c1e67fe0..cbfb7232c 100644 --- a/locales/ar.json +++ b/locales/ar.json @@ -164,7 +164,7 @@ "diagnosis_basesystem_kernel": "هذا الخادم يُشغّل نواة لينكس {kernel_version}", "diagnosis_basesystem_ynh_single_version": "{package} الإصدار: {version} ({repo})", "diagnosis_basesystem_ynh_main_version": "هذا الخادم يُشغّل YunoHost {main_version} ({repo})", - "diagnosis_everything_ok": "كل شيء على ما يرام في {category}!", + "diagnosis_everything_ok": "كل شيء يبدو على ما يرام في {category}!", "diagnosis_ip_connected_ipv4": "الخادم مُتّصل بالإنترنت عبر IPv4!", "diagnosis_ip_connected_ipv6": "الخادم مُتّصل بالإنترنت عبر IPv6!", "diagnosis_ip_not_connected_at_all": "يبدو أنّ الخادم غير مُتّصل بتاتا بالإنترنت!؟", @@ -172,5 +172,16 @@ "apps_already_up_to_date": "كافة التطبيقات مُحدّثة", "app_remove_after_failed_install": "جارٍ حذف التطبيق بعدما فشل تنصيبها…", "apps_catalog_updating": "جارٍ تحديث فهرس التطبيقات…", - "apps_catalog_update_success": "تم تحديث فهرس التطبيقات!" + "apps_catalog_update_success": "تم تحديث فهرس التطبيقات!", + "diagnosis_domain_expiration_error": "ستنتهي مدة صلاحية بعض النطاقات في القريب العاجل!", + "diagnosis_domain_expiration_warning": "ستنتهي مدة صلاحية بعض النطاقات قريبًا!", + "diagnosis_ports_could_not_diagnose_details": "خطأ: {error}", + "diagnosis_description_regenconf": "إعدادات النظام", + "diagnosis_description_mail": "البريد الإلكتروني", + "diagnosis_description_web": "الويب", + "diagnosis_description_systemresources": "موارد النظام", + "diagnosis_description_services": "حالة الخدمات", + "diagnosis_description_dnsrecords": "تسجيلات خدمة DNS", + "diagnosis_description_ip": "الإتصال بالإنترنت", + "diagnosis_description_basesystem": "النظام الأساسي" } diff --git a/locales/ca.json b/locales/ca.json index 64ee60477..52f2e7f87 100644 --- a/locales/ca.json +++ b/locales/ca.json @@ -127,7 +127,7 @@ "confirm_app_install_thirdparty": "PERILL! Aquesta aplicació no es part del catàleg d'aplicacions de YunoHost. La instal·lació d'aplicacions de terceres parts pot comprometre la integritat i seguretat del seu sistema. No hauríeu d'instal·lar-ne a no ser que sapigueu el que feu. No obtindreu CAP AJUDA si l'aplicació no funciona o trenca el sistema… Si accepteu el risc, escriviu «{answers:s}»", "custom_app_url_required": "Heu de especificar una URL per actualitzar la vostra aplicació personalitzada {app:s}", "admin_password_too_long": "Trieu una contrasenya de menys de 127 caràcters", - "dpkg_is_broken": "No es pot fer això en aquest instant perquè dpkg/APT (els gestors de paquets del sistema) sembla estar mal configurat… Podeu intentar solucionar-ho connectant-vos per SSH i executant «sudo dpkg --configure -a».", + "dpkg_is_broken": "No es pot fer això en aquest instant perquè dpkg/APT (els gestors de paquets del sistema) sembla estar mal configurat… Podeu intentar solucionar-ho connectant-vos per SSH i executant «sudo apt install --fix-broken» i/o «sudo dpkg --configure -a».", "domain_cannot_remove_main": "No es pot eliminar «{domain:s}» ja que és el domini principal, primer s'ha d'establir un nou domini principal utilitzant «yunohost domain main-domain -n »; aquí hi ha una llista dels possibles dominis: {other_domains:s}", "domain_cert_gen_failed": "No s'ha pogut generar el certificat", "domain_created": "S'ha creat el domini", @@ -390,7 +390,7 @@ "ssowat_conf_updated": "S'ha actualitzat la configuració SSOwat", "system_upgraded": "S'ha actualitzat el sistema", "system_username_exists": "El nom d'usuari ja existeix en la llista d'usuaris de sistema", - "this_action_broke_dpkg": "Aquesta acció a trencat dpkg/APT (els gestors de paquets del sistema)… Podeu intentar resoldre el problema connectant-vos amb SSH i executant «sudo dpkg --configure -a».", + "this_action_broke_dpkg": "Aquesta acció a trencat dpkg/APT (els gestors de paquets del sistema)… Podeu intentar resoldre el problema connectant-vos amb SSH i executant «sudo apt install --fix-broken» i/o «sudo dpkg --configure -a».", "tools_upgrade_at_least_one": "Especifiqueu «--apps», o «--system»", "tools_upgrade_cant_both": "No es poden actualitzar tant el sistema com les aplicacions al mateix temps", "tools_upgrade_cant_hold_critical_packages": "No es poden mantenir els paquets crítics…", @@ -648,5 +648,6 @@ "diagnosis_domain_expiration_warning": "Alguns dominis expiraran properament!", "diagnosis_domain_expiration_error": "Alguns dominis expiraran EN BREUS!", "diagnosis_domain_expires_in": "{domain} expirarà en {days} dies.", - "diagnosis_swap_tip": "Vigileu i tingueu en compte que els servidor està allotjant memòria d'intercanvi en una targeta SD o en l'emmagatzematge SSD, això pot reduir dràsticament l'esperança de vida del dispositiu." + "diagnosis_swap_tip": "Vigileu i tingueu en compte que els servidor està allotjant memòria d'intercanvi en una targeta SD o en l'emmagatzematge SSD, això pot reduir dràsticament l'esperança de vida del dispositiu.", + "restore_already_installed_apps": "No s'han pogut restaurar les següents aplicacions perquè ja estan instal·lades: {apps}" } diff --git a/locales/de.json b/locales/de.json index 5b372edfc..0c5b031ce 100644 --- a/locales/de.json +++ b/locales/de.json @@ -167,23 +167,23 @@ "certmanager_attempt_to_replace_valid_cert": "Du versuchst gerade eine richtiges und gültiges Zertifikat der Domain {domain:s} zu überschreiben! (Benutze --force , um diese Nachricht zu umgehen)", "certmanager_domain_unknown": "Unbekannte Domain '{domain:s}'", "certmanager_domain_cert_not_selfsigned": "Das Zertifikat der Domain {domain:s} ist kein selbstsigniertes Zertifikat. Bist du dir sicher, dass du es ersetzen willst? (Benutze dafür '--force')", - "certmanager_certificate_fetching_or_enabling_failed": "Es scheint so als wäre die Aktivierung des Zertifikats für die Domain {domain:s} fehlgeschlagen...", + "certmanager_certificate_fetching_or_enabling_failed": "Die Aktivierung des neuen Zertifikats für die Domain {domain:s} ist fehlgeschlagen…", "certmanager_attempt_to_renew_nonLE_cert": "Das Zertifikat der Domain '{domain:s}' wurde nicht von Let's Encrypt ausgestellt. Es kann nicht automatisch erneuert werden!", "certmanager_attempt_to_renew_valid_cert": "Das Zertifikat der Domain {domain:s} läuft nicht in Kürze ab! (Benutze --force um diese Nachricht zu umgehen)", "certmanager_domain_http_not_working": "Es scheint so, dass die Domain {domain:s} nicht über HTTP erreicht werden kann. Bitte überprüfe, ob deine DNS und nginx Konfiguration in Ordnung ist", "certmanager_error_no_A_record": "Kein DNS 'A' Eintrag für die Domain {domain:s} gefunden. Dein Domainname muss auf diese Maschine weitergeleitet werden, um ein Let's Encrypt Zertifikat installieren zu können! (Wenn du weißt was du tust, kannst du --no-checks benutzen, um diese Überprüfung zu überspringen. )", "certmanager_domain_dns_ip_differs_from_public_ip": "Der DNS 'A' Eintrag der Domain {domain:s} unterscheidet sich von dieser Server-IP. Wenn du gerade deinen A Eintrag verändert hast, warte bitte etwas, damit die Änderungen wirksam werden (du kannst die DNS Propagation mittels Website überprüfen) (Wenn du weißt was du tust, kannst du --no-checks benutzen, um diese Überprüfung zu überspringen. )", "certmanager_cannot_read_cert": "Es ist ein Fehler aufgetreten, als es versucht wurde das aktuelle Zertifikat für die Domain {domain:s} zu öffnen (Datei: {file:s}), Grund: {reason:s}", - "certmanager_cert_install_success_selfsigned": "Ein selbstsigniertes Zertifikat für die Domain {domain:s} wurde erfolgreich installiert!", - "certmanager_cert_install_success": "Für die Domain {domain:s} wurde erfolgreich ein Let's Encrypt installiert!", - "certmanager_cert_renew_success": "Das Let's Encrypt Zertifikat für die Domain {domain:s} wurde erfolgreich erneuert!", + "certmanager_cert_install_success_selfsigned": "Ein selbstsigniertes Zertifikat für die Domain {domain:s} wurde erfolgreich installiert", + "certmanager_cert_install_success": "Für die Domain {domain:s} wurde erfolgreich ein Let's Encrypt Zertifikat installiert.", + "certmanager_cert_renew_success": "Das Let's Encrypt Zertifikat für die Domain {domain:s} wurde erfolgreich erneuert.", "certmanager_hit_rate_limit": "Es wurden innerhalb kurzer Zeit schon zu viele Zertifikate für die exakt gleiche Domain {domain:s} ausgestellt. Bitte versuche es später nochmal. Besuche https://letsencrypt.org/docs/rate-limits/ für mehr Informationen", "certmanager_cert_signing_failed": "Das neue Zertifikat konnte nicht signiert werden", "certmanager_no_cert_file": "Die Zertifikatsdatei für die Domain {domain:s} (Datei: {file:s}) konnte nicht gelesen werden", "certmanager_conflicting_nginx_file": "Die Domain konnte nicht für die ACME challenge vorbereitet werden: Die nginx Konfigurationsdatei {filepath:s} verursacht Probleme und sollte vorher entfernt werden", "domain_cannot_remove_main": "Die primäre Domain konnten nicht entfernt werden. Lege zuerst einen neue primäre Domain fest", "certmanager_self_ca_conf_file_not_found": "Die Konfigurationsdatei der Zertifizierungsstelle für selbstsignierte Zertifikate wurde nicht gefunden (Datei {file:s})", - "certmanager_acme_not_configured_for_domain": "Das Zertifikat für die Domain '{domain:s}' scheint nicht richtig installiert zu sein. Bitte führe den Befehl cert-install für diese Domain nochmals aus.", + "certmanager_acme_not_configured_for_domain": "Die ACME Challenge kann im Moment nicht für {domain} ausgeführt werden, weil in ihrer nginx conf das entsprechende Code-Snippet fehlt... Bitte stellen Sie sicher, dass Ihre nginx-Konfiguration mit 'yunohost tools regen-conf nginx --dry-run --with-diff' auf dem neuesten Stand ist.", "certmanager_unable_to_parse_self_CA_name": "Der Name der Zertifizierungsstelle für selbstsignierte Zertifikate konnte nicht analysiert werden (Datei: {file:s})", "certmanager_http_check_timeout": "Eine Zeitüberschreitung ist aufgetreten, als der Server versuchte sich selbst über HTTP mit der öffentlichen IP (Domain '{domain:s}' mit der IP '{ip:s}') zu erreichen. Möglicherweise ist dafür hairpinning oder eine falsch konfigurierte Firewall/Router deines Servers dafür verantwortlich.", "certmanager_couldnt_fetch_intermediate_cert": "Eine Zeitüberschreitung ist aufgetreten als der Server versuchte die Teilzertifikate von Let's Encrypt zusammenzusetzen. Die Installation/Erneuerung des Zertifikats wurde abgebrochen — bitte versuche es später erneut.", @@ -338,5 +338,7 @@ "diagnosis_found_warnings": "Habe {warnings} Ding(e) gefunden, die verbessert werden könnten für {category}.", "diagnosis_ip_dnsresolution_working": "Domänen-Namens-Auflösung funktioniert!", "diagnosis_ip_weird_resolvconf": "DNS Auflösung scheint zu funktionieren, aber sei vorsichtig wenn du eine eigene /etc/resolv.conf verwendest.", - "diagnosis_display_tip": "Um die gefundenen Probleme zu sehen, kannst Du zum Diagnose-Bereich des webadmin gehen, oder 'yunohost diagnosis show --issues' in der Kommandozeile ausführen." + "diagnosis_display_tip": "Um die gefundenen Probleme zu sehen, kannst Du zum Diagnose-Bereich des webadmin gehen, oder 'yunohost diagnosis show --issues' in der Kommandozeile ausführen.", + "backup_archive_corrupted": "Das Backup-Archiv '{archive}' scheint beschädigt: {error}", + "backup_archive_cant_retrieve_info_json": "Die Informationen für das Archiv '{archive}' konnten nicht geladen werden... Die Datei info.json wurde nicht gefunden (oder ist kein gültiges json)." } diff --git a/locales/en.json b/locales/en.json index a1cfd80a6..2ce934400 100644 --- a/locales/en.json +++ b/locales/en.json @@ -51,6 +51,7 @@ "app_upgrade_script_failed": "An error occurred inside the app upgrade script", "app_upgrade_some_app_failed": "Some apps could not be upgraded", "app_upgraded": "{app:s} upgraded", + "app_packaging_format_not_supported": "This app cannot be installed because its packaging format is not supported by your Yunohost version. You should probably consider upgrading your system.", "apps_already_up_to_date": "All apps are already up-to-date", "apps_catalog_init_success": "App catalog system initialized!", "apps_catalog_updating": "Updating application catalog…", @@ -173,8 +174,9 @@ "diagnosis_dns_good_conf": "DNS records are correctly configured for domain {domain} (category {category})", "diagnosis_dns_bad_conf": "Some DNS records are missing or incorrect for domain {domain} (category {category})", "diagnosis_dns_missing_record": "According to the recommended DNS configuration, you should add a DNS record with the following info.
Type: {type}
Name: {name}
Value: {value}", - "diagnosis_dns_discrepancy": "The following DNS record does not seem to follow the recommended configuration:
Type: {type}
Name: {name}
Current value: {current}
Excepted value: {value}", + "diagnosis_dns_discrepancy": "The following DNS record does not seem to follow the recommended configuration:
Type: {type}
Name: {name}
Current value: {current}
Expected value: {value}", "diagnosis_dns_point_to_doc": "Please check the documentation at https://yunohost.org/dns_config if you need help about configuring DNS records.", + "diagnosis_dns_try_dyndns_update_force": "This domain's DNS configuration should automatically be managed by Yunohost. If that's not the case, you can try to force an update using yunohost dyndns update --force.", "diagnosis_domain_expiration_not_found": "Unable to check the expiration date for some domains", "diagnosis_domain_not_found_details": "The domain {domain} doesn't exist in WHOIS database or is expired!", "diagnosis_domain_expiration_not_found_details": "The WHOIS information for domain {domain} doesn't seem to contain the information about the expiration date?", diff --git a/locales/fr.json b/locales/fr.json index b92c828a2..38a9ad5a7 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -509,7 +509,7 @@ "diagnosis_ip_not_connected_at_all": "Le serveur ne semble pas du tout connecté à Internet !?", "diagnosis_ip_weird_resolvconf": "La résolution DNS semble fonctionner, mais il semble que vous utilisez un /etc/resolv.conf personnalisé.", "diagnosis_ip_weird_resolvconf_details": "Le fichier /etc/resolv.conf doit être un lien symbolique vers /etc/resolvconf/run/resolv.conf lui-même pointant vers 127.0.0.1 (dnsmasq). Si vous souhaitez configurer manuellement les résolveurs DNS, veuillez modifier /etc/resolv.dnsmasq.conf.", - "diagnosis_dns_missing_record": "Selon la configuration DNS recommandée, vous devez ajouter un enregistrement DNS\nType : {type}\nNom : {name}\nValeur : {value}", + "diagnosis_dns_missing_record": "Selon la configuration DNS recommandée, vous devez ajouter un enregistrement DNS
Type : {type}
Nom : {name}
Valeur: {value}", "diagnosis_diskusage_ok": "L’espace de stockage {mountpoint} (sur le périphérique {device}) a encore {free} ({free_percent}%) espace restant (sur {total}) !", "diagnosis_ram_ok": "Le système dispose encore de {available} ({available_percent}%) de RAM sur {total}.", "diagnosis_regenconf_allgood": "Tous les fichiers de configuration sont conformes à la configuration recommandée !", @@ -535,7 +535,7 @@ "diagnosis_ip_broken_resolvconf": "La résolution du nom de domaine semble être cassée sur votre serveur, ce qui semble lié au fait que /etc/resolv.conf ne pointe pas vers 127.0.0.1.", "diagnosis_dns_good_conf": "Les enregistrements DNS sont correctement configurés pour le domaine {domain} (catégorie {category})", "diagnosis_dns_bad_conf": "Certains enregistrements DNS sont manquants ou incorrects pour le domaine {domain} (catégorie {category})", - "diagnosis_dns_discrepancy": "L’enregistrement DNS de type {type} et nom {name} ne correspond pas à la configuration recommandée.\nValeur actuelle : {current}\nValeur attendue : {value}", + "diagnosis_dns_discrepancy": "Cet enregistrement DNS ne semble pas correspondre à la configuration recommandée :
Type : {type}
Nom : {name}
Valeur actuelle : {current}
Valeur attendue : {value}", "diagnosis_services_bad_status": "Le service {service} est {status} :-(", "diagnosis_diskusage_verylow": "L'espace de stockage {mountpoint} (sur l’appareil {device}) ne dispose que de {free} ({free_percent}%) espace restant (sur {total}). Vous devriez vraiment envisager de nettoyer de l’espace !", "diagnosis_diskusage_low": "L'espace de stockage {mountpoint} (sur l'appareil {device}) ne dispose que de {free} ({free_percent}%) espace restant (sur {total}). Faites attention.", @@ -618,7 +618,7 @@ "diagnosis_mail_queue_too_big": "Trop d’e-mails en attente dans la file d'attente ({nb_pending} e-mails)", "global_settings_setting_smtp_allow_ipv6": "Autoriser l'utilisation d’IPv6 pour recevoir et envoyer du courrier", "diagnosis_security_all_good": "Aucune vulnérabilité de sécurité critique n’a été trouvée.", - "diagnosis_display_tip": "Pour voir les problèmes détectés, vous pouvez accéder à la section Diagnostic du webadmin ou exécuter « yunohost diagnostic show --issues » à partir de la ligne de commande.", + "diagnosis_display_tip": "Pour voir les problèmes détectés, vous pouvez accéder à la section Diagnostic du webadmin ou exécuter « yunohost diagnosis show --issues » à partir de la ligne de commande.", "diagnosis_ip_global": "IP globale : {global}", "diagnosis_ip_local": "IP locale : {local}", "diagnosis_dns_point_to_doc": "Veuillez consulter la documentation sur https://yunohost.org/dns_config si vous avez besoin d’aide pour configurer les enregistrements DNS.", @@ -637,7 +637,7 @@ "diagnosis_http_hairpinning_issue_details": "C'est probablement à cause de la box/routeur de votre fournisseur d'accès internet. Par conséquent, les personnes extérieures à votre réseau local pourront accéder à votre serveur comme prévu, mais pas les personnes internes au réseau local (comme vous, probablement ?) si elles utilisent le nom de domaine ou l'IP globale. Vous pourrez peut-être améliorer la situation en consultant https://yunohost.org/dns_local_network", "diagnosis_http_partially_unreachable": "Le domaine {domain} semble inaccessible en HTTP depuis l’extérieur du réseau local en IPv{failed}, bien qu’il fonctionne en IPv{passed}.", "diagnosis_http_nginx_conf_not_up_to_date": "La configuration Nginx de ce domaine semble avoir été modifiée manuellement et empêche YunoHost de diagnostiquer si elle est accessible en HTTP.", - "diagnosis_http_nginx_conf_not_up_to_date_details": "Pour corriger la situation, inspectez la différence avec la ligne de commande en utilisant les outils yunohost regen-conf nginx --dry-run --with-diff et si vous êtes d’accord, appliquez les modifications avec yunohost tools regen-conf nginx --force.", + "diagnosis_http_nginx_conf_not_up_to_date_details": "Pour corriger la situation, inspectez la différence avec la ligne de commande en utilisant les outils yunohost tools regen-conf nginx --dry-run --with-diff et si vous êtes d’accord, appliquez les modifications avec yunohost tools regen-conf nginx --force.", "backup_archive_cant_retrieve_info_json": "Impossible d'avoir des informations sur l'archive '{archive}' ... Le fichier info.json ne peut pas être trouvé (ou n'est pas un fichier json valide).", "backup_archive_corrupted": "Il semble que l'archive de la sauvegarde '{archive}' est corrompue : {error}", "diagnosis_ip_no_ipv6_tip": "L'utilisation de IPv6 n'est pas obligatoire pour le fonctionnement de votre serveur, mais cela contribue à la santé d'Internet dans son ensemble. IPv6 généralement configuré automatiquement par votre système ou votre FAI s'il est disponible. Autrement, vous devrez prendre quelque minutes pour le configurer manuellement à l'aide de cette documentation: https://yunohost.org/#/ipv6. Si vous ne pouvez pas activer IPv6 ou si c'est trop technique pour vous, vous pouvez aussi ignorer cet avertissement sans que cela pose problème.", diff --git a/locales/it.json b/locales/it.json index 79204320b..f254d3407 100644 --- a/locales/it.json +++ b/locales/it.json @@ -45,8 +45,8 @@ "ask_new_admin_password": "Nuova password dell'amministrazione", "backup_app_failed": "Non è possibile fare il backup dell'applicazione '{app:s}'", "backup_archive_app_not_found": "L'applicazione '{app:s}' non è stata trovata nel archivio di backup", - "app_argument_choice_invalid": "Scelta non valida per l'argomento '{name:s}', deve essere uno di {choices:s}", - "app_argument_invalid": "Valore non valido per '{name:s}': {error:s}", + "app_argument_choice_invalid": "Usa una delle seguenti scelte '{choices:s}' per il parametro '{name:s}'", + "app_argument_invalid": "Scegli un valore valido per il parametro '{name:s}': {error:s}", "app_argument_required": "L'argomento '{name:s}' è requisito", "app_id_invalid": "Identificativo dell'applicazione non valido", "app_unsupported_remote_type": "Il tipo remoto usato per l'applicazione non è supportato", @@ -171,9 +171,9 @@ "certmanager_attempt_to_renew_nonLE_cert": "Il certificato per il dominio {domain:s} non è emesso da Let's Encrypt. Impossibile rinnovarlo automaticamente!", "certmanager_attempt_to_renew_valid_cert": "Il certificato per il dominio {domain:s} non è a scadere! Usa --force per ignorare", "certmanager_domain_http_not_working": "Sembra che non sia possibile accedere al dominio {domain:s} attraverso HTTP. Verifica la configurazione del DNS e di nginx", - "app_already_installed_cant_change_url": "Questa applicazione è già installata. L'URL non può essere cambiato solo da questa funzione. Guarda se `app changeurl` è disponibile.", + "app_already_installed_cant_change_url": "Questa applicazione è già installata. L'URL non può essere cambiato solo da questa funzione. Controlla se `app changeurl` è disponibile.", "app_already_up_to_date": "{app:s} è già aggiornata", - "app_change_url_failed_nginx_reload": "Riavvio di nginx fallito. Questo è il risultato di 'nginx -t':\n{nginx_errors:s}", + "app_change_url_failed_nginx_reload": "Non riesco a riavviare NGINX. Questo è il risultato di 'nginx -t':\n{nginx_errors:s}", "app_change_url_identical_domains": "Il vecchio ed il nuovo dominio/percorso_url sono identici ('{domain:s}{path:s}'), nessuna operazione necessaria.", "app_change_url_no_script": "L'applicazione '{app_name:s}' non supporta ancora la modifica dell'URL. Forse dovresti aggiornare l'applicazione.", "app_change_url_success": "URL dell'applicazione {app:s} cambiato con successo in {domain:s}{path:s}", @@ -233,7 +233,7 @@ "password_too_simple_4": "La password deve essere lunga almeno 12 caratteri e contenere numeri, maiuscole e minuscole", "users_available": "Utenti disponibili:", "yunohost_ca_creation_success": "L'autorità di certificazione locale è stata creata.", - "app_action_cannot_be_ran_because_required_services_down": "Questa app richiede alcuni servizi che attualmente non sono attivi. Prima di continuare, dovresti provare a riavviare i seguenti servizi (e possibilmente capire perchè questi non siano attivi) : {services}", + "app_action_cannot_be_ran_because_required_services_down": "I seguenti servizi dovrebbero essere in funzione per completare questa azione: {services}. Prova a riavviarli per proseguire (e possibilmente cercare di capire come ma non funzionano più).", "backup_output_symlink_dir_broken": "Hai un collegamento errato alla tua cartella di archiviazione '{path:s}'. Potresti avere delle impostazioni particolari per salvare i tuoi dati su un altro spazio, in questo caso probabilmente ti sei scordato di rimontare o collegare il tuo hard disk o la chiavetta usb.", "certmanager_conflicting_nginx_file": "Impossibile preparare il dominio per il controllo ACME: il file di configurazione nginx {filepath:s} è in conflitto e dovrebbe essere prima rimosso", "certmanager_couldnt_fetch_intermediate_cert": "Tempo scaduto durante il tentativo di recupero di un certificato intermedio da Let's Encrypt. Installazione/rinnovo non riuscito - per favore riprova più tardi.", @@ -335,5 +335,5 @@ "migration_0003_not_jessie": "La distribuzione attuale non è Jessie!", "migration_0003_system_not_fully_up_to_date": "Il tuo sistema non è completamente aggiornato. Per favore prima esegui un aggiornamento normale prima di migrare a stretch.", "this_action_broke_dpkg": "Questa azione ha danneggiato dpkg/apt (i gestori di pacchetti del sistema)… Puoi provare a risolvere questo problema connettendoti via SSH ed eseguendo `sudo dpkg --configure -a`.", - "app_action_broke_system": "Questa azione sembra avere roto servizi importanti: {services}" -} \ No newline at end of file + "app_action_broke_system": "Questa azione sembra avere rotto questi servizi importanti: {services}" +} diff --git a/pytest.ini b/pytest.ini index fb4d5b265..709e0e0b9 100644 --- a/pytest.ini +++ b/pytest.ini @@ -10,3 +10,5 @@ markers = with_legacy_app_installed with_backup_recommended_app_installed_with_ynh_restore with_permission_app_installed +filterwarnings = + ignore::urllib3.exceptions.InsecureRequestWarning \ No newline at end of file diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 12edbcfe5..22ce8af93 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -414,6 +414,7 @@ def app_upgrade(app=[], url=None, file=None): """ from yunohost.hook import hook_add, hook_remove, hook_exec, hook_callback from yunohost.permission import permission_sync_to_user, user_permission_list + from yunohost.regenconf import manually_modified_files apps = app # If no app is specified, upgrade all apps @@ -477,6 +478,9 @@ def app_upgrade(app=[], url=None, file=None): env_dict["YNH_APP_INSTANCE_NUMBER"] = str(app_instance_nb) env_dict["YNH_APP_LABEL"] = user_permission_list(full=True, ignore_system_perms=True, full_path=False)['permissions'][app_id+".main"]['label'] + # We'll check that the app didn't brutally edit some system configuration + manually_modified_files_before_install = manually_modified_files() + # Attempt to patch legacy helpers ... _patch_legacy_helpers(extracted_app_folder) @@ -527,6 +531,12 @@ def app_upgrade(app=[], url=None, file=None): logger.error(m18n.n("app_upgrade_failed", app=app_instance_name, error=str(e))) failure_message_with_debug_instructions = operation_logger.error(str(e)) + # We'll check that the app didn't brutally edit some system configuration + manually_modified_files_after_install = manually_modified_files() + manually_modified_files_by_app = set(manually_modified_files_after_install) - set(manually_modified_files_before_install) + if manually_modified_files_by_app: + logger.error("Packagers /!\\ This app manually modified some system configuration files! This should not happen! If you need to do so, you should implement a proper conf_regen hook. Those configuration were affected:\n - " + '\n -'.join(manually_modified_files_by_app)) + # If upgrade failed or broke the system, # raise an error and interrupt all other pending upgrades if upgrade_failed or broke_the_system: @@ -590,6 +600,7 @@ def app_install(operation_logger, app, label=None, args=None, no_remove_on_failu from yunohost.hook import hook_add, hook_remove, hook_exec, hook_callback from yunohost.log import OperationLogger from yunohost.permission import user_permission_list, permission_create, permission_url, permission_delete, permission_sync_to_user, user_permission_update + from yunohost.regenconf import manually_modified_files # Fetch or extract sources if not os.path.exists(INSTALL_TMP): @@ -678,12 +689,14 @@ def app_install(operation_logger, app, label=None, args=None, no_remove_on_failu args_dict = {} if not args else \ dict(urlparse.parse_qsl(args, keep_blank_values=True)) args_odict = _parse_args_from_manifest(manifest, 'install', args=args_dict) - args_list = [value[0] for value in args_odict.values()] - args_list.append(app_instance_name) # Validate domain / path availability for webapps _validate_and_normalize_webpath(manifest, args_odict, extracted_app_folder) + # build arg list tq + args_list = [value[0] for value in args_odict.values()] + args_list.append(app_instance_name) + # Attempt to patch legacy helpers ... _patch_legacy_helpers(extracted_app_folder) @@ -700,6 +713,9 @@ def app_install(operation_logger, app, label=None, args=None, no_remove_on_failu # Start register change on system operation_logger.extra.update({'env': env_dict}) + # We'll check that the app didn't brutally edit some system configuration + manually_modified_files_before_install = manually_modified_files() + # Tell the operation_logger to redact all password-type args # Also redact the % escaped version of the password that might appear in # the 'args' section of metadata (relevant for password with non-alphanumeric char) @@ -783,6 +799,12 @@ def app_install(operation_logger, app, label=None, args=None, no_remove_on_failu logger.error(m18n.n("app_install_failed", app=app_id, error=str(e))) failure_message_with_debug_instructions = operation_logger.error(str(e)) + # We'll check that the app didn't brutally edit some system configuration + manually_modified_files_after_install = manually_modified_files() + manually_modified_files_by_app = set(manually_modified_files_after_install) - set(manually_modified_files_before_install) + if manually_modified_files_by_app: + logger.error("Packagers /!\\ This app manually modified some system configuration files! This should not happen! If you need to do so, you should implement a proper conf_regen hook. Those configuration were affected:\n - " + '\n -'.join(manually_modified_files_by_app)) + # If the install failed or broke the system, we remove it if install_failed or broke_the_system: @@ -1864,6 +1886,9 @@ def _get_app_settings(app_id): with open(os.path.join( APPS_SETTING_PATH, app_id, 'settings.yml')) as f: settings = yaml.load(f) + # If label contains unicode char, this may later trigger issues when building strings... + # FIXME: this should be propagated to read_yaml so that this fix applies everywhere I think... + settings = {k:_encode_string(v) for k,v in settings.items()} if app_id == settings['id']: return settings except (IOError, TypeError, KeyError): @@ -2321,6 +2346,11 @@ def _encode_string(value): def _check_manifest_requirements(manifest, app_instance_name): """Check if required packages are met from the manifest""" + + packaging_format = int(manifest.get('packaging_format', 0)) + if packaging_format not in [0, 1]: + raise YunohostError("app_packaging_format_not_supported") + requirements = manifest.get('requirements', dict()) if not requirements: diff --git a/src/yunohost/diagnosis.py b/src/yunohost/diagnosis.py index 4fad86ffd..76c4d1243 100644 --- a/src/yunohost/diagnosis.py +++ b/src/yunohost/diagnosis.py @@ -452,7 +452,7 @@ class Diagnoser(): key = "diagnosis_description_" + id_ descr = m18n.n(key) # If no description available, fallback to id - return descr if descr != key else id_ + return descr if descr.decode('utf-8') != key else id_ @staticmethod def i18n(report, force_remove_html_tags=False): @@ -558,9 +558,10 @@ def _list_diagnosis_categories(): def _email_diagnosis_issues(): from yunohost.domain import _get_maindomain - from_ = "diagnosis@%s (Automatic diagnosis)" % _get_maindomain() + maindomain = _get_maindomain() + from_ = "diagnosis@%s (Automatic diagnosis on %s)" % (maindomain, maindomain) to_ = "root" - subject_ = "Issues found by automatic diagnosis" + subject_ = "Issues found by automatic diagnosis on %s" % maindomain disclaimer = "The automatic diagnosis on your YunoHost server identified some issues on your server. You will find a description of the issues below. You can manage those issues in the 'Diagnosis' section in your webadmin." diff --git a/src/yunohost/hook.py b/src/yunohost/hook.py index dbfd7eceb..b57300f54 100644 --- a/src/yunohost/hook.py +++ b/src/yunohost/hook.py @@ -432,7 +432,7 @@ def _hook_exec_bash(path, args, no_trace, chdir, env, user, return_format, logge returncontent[key] = value else: - raise YunohostError("Excepted value for return_format is either 'json' or 'plain_dict', got '%s'" % return_format) + raise YunohostError("Expected value for return_format is either 'json' or 'plain_dict', got '%s'" % return_format) finally: stdreturndir = os.path.split(stdreturn)[0] os.remove(stdreturn) diff --git a/src/yunohost/log.py b/src/yunohost/log.py index de84280f0..5e3a1f411 100644 --- a/src/yunohost/log.py +++ b/src/yunohost/log.py @@ -40,8 +40,8 @@ from moulinette.utils.filesystem import read_file, read_yaml CATEGORIES_PATH = '/var/log/yunohost/categories/' OPERATIONS_PATH = '/var/log/yunohost/categories/operation/' -CATEGORIES = ['operation', 'history', 'package', 'system', 'access', 'service', - 'app'] +#CATEGORIES = ['operation', 'history', 'package', 'system', 'access', 'service', 'app'] +CATEGORIES = ['operation'] METADATA_FILE_EXT = '.yml' LOG_FILE_EXT = '.log' RELATED_CATEGORIES = ['app', 'domain', 'group', 'service', 'user'] @@ -63,7 +63,7 @@ def log_list(category=[], limit=None, with_details=False): # In cli we just display `operation` logs by default if not categories: - categories = ["operation"] if not is_api else CATEGORIES + categories = CATEGORIES result = collections.OrderedDict() for category in categories: diff --git a/src/yunohost/regenconf.py b/src/yunohost/regenconf.py index d1c90ceee..53deb7073 100644 --- a/src/yunohost/regenconf.py +++ b/src/yunohost/regenconf.py @@ -346,6 +346,11 @@ def _save_regenconf_infos(infos): Keyword argument: categories -- A dict containing the regenconf infos """ + + # Ugly hack to get rid of legacy glances stuff + if "glances" in infos: + del infos["glances"] + try: with open(REGEN_CONF_FILE, 'w') as f: yaml.safe_dump(infos, f, default_flow_style=False) diff --git a/src/yunohost/service.py b/src/yunohost/service.py index bc082da21..faaf70cdc 100644 --- a/src/yunohost/service.py +++ b/src/yunohost/service.py @@ -354,7 +354,7 @@ def _get_and_format_service_status(service, infos): # that mean that we don't have a translation for this string # that's the only way to test for that for now # if we don't have it, uses the one provided by systemd - if description == translation_key: + if description.decode('utf-8') == translation_key: description = str(raw_status.get("Description", "")) output = { @@ -589,7 +589,7 @@ def _get_services(): """ try: with open('/etc/yunohost/services.yml', 'r') as f: - services = yaml.load(f) + services = yaml.load(f) or {} except: return {} diff --git a/src/yunohost/tests/conftest.py b/src/yunohost/tests/conftest.py index 073c880f8..d5f3ed186 100644 --- a/src/yunohost/tests/conftest.py +++ b/src/yunohost/tests/conftest.py @@ -19,6 +19,9 @@ def clone_test_app(request): else: os.system("cd %s/apps && git pull > /dev/null 2>&1" % cwd) +def get_test_apps_dir(): + cwd = os.path.split(os.path.realpath(__file__))[0] + return os.path.join(cwd, "apps") @contextmanager def message(mocker, key, **kwargs): diff --git a/src/yunohost/tests/test_apps.py b/src/yunohost/tests/test_apps.py index c2c7b8415..0f4c3749a 100644 --- a/src/yunohost/tests/test_apps.py +++ b/src/yunohost/tests/test_apps.py @@ -4,7 +4,7 @@ import pytest import shutil import requests -from conftest import message, raiseYunohostError +from conftest import message, raiseYunohostError, get_test_apps_dir from moulinette.utils.filesystem import mkdir @@ -150,7 +150,7 @@ def app_is_exposed_on_http(domain, path, message_in_page): def install_legacy_app(domain, path, public=True): app_install( - "./tests/apps/legacy_app_ynh", + os.path.join(get_test_apps_dir(), "legacy_app_ynh"), args="domain=%s&path=%s&is_public=%s" % (domain, path, 1 if public else 0), force=True, ) @@ -159,14 +159,14 @@ def install_legacy_app(domain, path, public=True): def install_full_domain_app(domain): app_install( - "./tests/apps/full_domain_app_ynh", args="domain=%s" % domain, force=True + os.path.join(get_test_apps_dir(), "full_domain_app_ynh"), args="domain=%s" % domain, force=True ) def install_break_yo_system(domain, breakwhat): app_install( - "./tests/apps/break_yo_system_ynh", + os.path.join(get_test_apps_dir(), "break_yo_system_ynh"), args="domain=%s&breakwhat=%s" % (domain, breakwhat), force=True, ) @@ -376,7 +376,7 @@ def test_systemfuckedup_during_app_upgrade(mocker, secondary_domain): with pytest.raises(YunohostError): with message(mocker, "app_action_broke_system"): - app_upgrade("break_yo_system", file="./tests/apps/break_yo_system_ynh") + app_upgrade("break_yo_system", file=os.path.join(get_test_apps_dir(), "break_yo_system_ynh")) def test_failed_multiple_app_upgrade(mocker, secondary_domain): @@ -389,7 +389,7 @@ def test_failed_multiple_app_upgrade(mocker, secondary_domain): app_upgrade( ["break_yo_system", "legacy_app"], file={ - "break_yo_system": "./tests/apps/break_yo_system_ynh", - "legacy": "./tests/apps/legacy_app_ynh", + "break_yo_system": os.path.join(get_test_apps_dir(), "break_yo_system_ynh"), + "legacy": os.path.join(get_test_apps_dir(), "legacy_app_ynh"), }, ) diff --git a/src/yunohost/tests/test_apps_arguments_parsing.py b/src/yunohost/tests/test_apps_arguments_parsing.py index 8fe3d7728..a3d5b7f09 100644 --- a/src/yunohost/tests/test_apps_arguments_parsing.py +++ b/src/yunohost/tests/test_apps_arguments_parsing.py @@ -1,9 +1,13 @@ +import sys import pytest -from collections import OrderedDict + from mock import patch +from StringIO import StringIO +from collections import OrderedDict from moulinette import msignals +from yunohost import domain, user, app from yunohost.app import _parse_args_in_yunohost_format from yunohost.utils.error import YunohostError @@ -36,28 +40,21 @@ def test_parse_args_in_yunohost_format_empty(): def test_parse_args_in_yunohost_format_string(): - questions = [{ - "name": "some_string", - "type": "string", - }] + questions = [{"name": "some_string", "type": "string",}] answers = {"some_string": "some_value"} expected_result = OrderedDict({"some_string": ("some_value", "string")}) assert _parse_args_in_yunohost_format(answers, questions) == expected_result def test_parse_args_in_yunohost_format_string_default_type(): - questions = [{ - "name": "some_string", - }] + questions = [{"name": "some_string",}] answers = {"some_string": "some_value"} expected_result = OrderedDict({"some_string": ("some_value", "string")}) assert _parse_args_in_yunohost_format(answers, questions) == expected_result def test_parse_args_in_yunohost_format_string_no_input(): - questions = [{ - "name": "some_string", - }] + questions = [{"name": "some_string",}] answers = {} with pytest.raises(YunohostError): @@ -65,10 +62,7 @@ def test_parse_args_in_yunohost_format_string_no_input(): def test_parse_args_in_yunohost_format_string_input(): - questions = [{ - "name": "some_string", - "ask": "some question", - }] + questions = [{"name": "some_string", "ask": "some question",}] answers = {} expected_result = OrderedDict({"some_string": ("some_value", "string")}) @@ -78,9 +72,7 @@ def test_parse_args_in_yunohost_format_string_input(): @pytest.mark.skip # that shit should work x( def test_parse_args_in_yunohost_format_string_input_no_ask(): - questions = [{ - "name": "some_string", - }] + questions = [{"name": "some_string",}] answers = {} expected_result = OrderedDict({"some_string": ("some_value", "string")}) @@ -89,21 +81,1118 @@ def test_parse_args_in_yunohost_format_string_input_no_ask(): def test_parse_args_in_yunohost_format_string_no_input_optional(): - questions = [{ - "name": "some_string", - "optional": True, - }] + questions = [{"name": "some_string", "optional": True,}] answers = {} expected_result = OrderedDict({"some_string": ("", "string")}) assert _parse_args_in_yunohost_format(answers, questions) == expected_result +def test_parse_args_in_yunohost_format_string_optional_with_input(): + questions = [{"name": "some_string", "ask": "some question", "optional": True,}] + answers = {} + expected_result = OrderedDict({"some_string": ("some_value", "string")}) + + with patch.object(msignals, "prompt", return_value="some_value"): + assert _parse_args_in_yunohost_format(answers, questions) == expected_result + + +@pytest.mark.skip # this should work without ask +def test_parse_args_in_yunohost_format_string_optional_with_input_without_ask(): + questions = [{"name": "some_string", "optional": True,}] + answers = {} + expected_result = OrderedDict({"some_string": ("some_value", "string")}) + + with patch.object(msignals, "prompt", return_value="some_value"): + assert _parse_args_in_yunohost_format(answers, questions) == expected_result + + def test_parse_args_in_yunohost_format_string_no_input_default(): - questions = [{ - "name": "some_string", - "ask": "some question", - "default": "some_value", - }] + questions = [ + {"name": "some_string", "ask": "some question", "default": "some_value",} + ] answers = {} expected_result = OrderedDict({"some_string": ("some_value", "string")}) assert _parse_args_in_yunohost_format(answers, questions) == expected_result + + +def test_parse_args_in_yunohost_format_string_input_test_ask(): + ask_text = "some question" + questions = [{"name": "some_string", "ask": ask_text,}] + answers = {} + + with patch.object(msignals, "prompt", return_value="some_value") as prompt: + _parse_args_in_yunohost_format(answers, questions) + prompt.assert_called_with(ask_text, False) + + +def test_parse_args_in_yunohost_format_string_input_test_ask_with_default(): + ask_text = "some question" + default_text = "some example" + questions = [{"name": "some_string", "ask": ask_text, "default": default_text,}] + answers = {} + + with patch.object(msignals, "prompt", return_value="some_value") as prompt: + _parse_args_in_yunohost_format(answers, questions) + prompt.assert_called_with("%s (default: %s)" % (ask_text, default_text), False) + + +@pytest.mark.skip # we should do something with this example +def test_parse_args_in_yunohost_format_string_input_test_ask_with_example(): + ask_text = "some question" + example_text = "some example" + questions = [{"name": "some_string", "ask": ask_text, "example": example_text,}] + answers = {} + + with patch.object(msignals, "prompt", return_value="some_value") as prompt: + _parse_args_in_yunohost_format(answers, questions) + assert ask_text in prompt.call_args[0][0] + assert example_text in prompt.call_args[0][0] + + +@pytest.mark.skip # we should do something with this help +def test_parse_args_in_yunohost_format_string_input_test_ask_with_help(): + ask_text = "some question" + help_text = "some_help" + questions = [{"name": "some_string", "ask": ask_text, "help": help_text,}] + answers = {} + + with patch.object(msignals, "prompt", return_value="some_value") as prompt: + _parse_args_in_yunohost_format(answers, questions) + assert ask_text in prompt.call_args[0][0] + assert help_text in prompt.call_args[0][0] + + +def test_parse_args_in_yunohost_format_string_with_choice(): + questions = [{"name": "some_string", "type": "string", "choices": ["fr", "en"]}] + answers = {"some_string": "fr"} + expected_result = OrderedDict({"some_string": ("fr", "string")}) + assert _parse_args_in_yunohost_format(answers, questions) == expected_result + + +def test_parse_args_in_yunohost_format_string_with_choice_prompt(): + questions = [{"name": "some_string", "type": "string", "choices": ["fr", "en"]}] + answers = {"some_string": "fr"} + expected_result = OrderedDict({"some_string": ("fr", "string")}) + with patch.object(msignals, "prompt", return_value="fr"): + assert _parse_args_in_yunohost_format(answers, questions) == expected_result + + +def test_parse_args_in_yunohost_format_string_with_choice_bad(): + questions = [{"name": "some_string", "type": "string", "choices": ["fr", "en"]}] + answers = {"some_string": "bad"} + + with pytest.raises(YunohostError): + assert _parse_args_in_yunohost_format(answers, questions) + + +def test_parse_args_in_yunohost_format_string_with_choice_ask(): + ask_text = "some question" + choices = ["fr", "en", "es", "it", "ru"] + questions = [{"name": "some_string", "ask": ask_text, "choices": choices,}] + answers = {} + + with patch.object(msignals, "prompt", return_value="ru") as prompt: + _parse_args_in_yunohost_format(answers, questions) + assert ask_text in prompt.call_args[0][0] + + for choice in choices: + assert choice in prompt.call_args[0][0] + + +def test_parse_args_in_yunohost_format_string_with_choice_default(): + questions = [ + { + "name": "some_string", + "type": "string", + "choices": ["fr", "en"], + "default": "en", + } + ] + answers = {} + expected_result = OrderedDict({"some_string": ("en", "string")}) + assert _parse_args_in_yunohost_format(answers, questions) == expected_result + + +def test_parse_args_in_yunohost_format_password(): + questions = [{"name": "some_password", "type": "password",}] + answers = {"some_password": "some_value"} + expected_result = OrderedDict({"some_password": ("some_value", "password")}) + assert _parse_args_in_yunohost_format(answers, questions) == expected_result + + +def test_parse_args_in_yunohost_format_password_no_input(): + questions = [{"name": "some_password", "type": "password",}] + answers = {} + + with pytest.raises(YunohostError): + _parse_args_in_yunohost_format(answers, questions) + + +def test_parse_args_in_yunohost_format_password_input(): + questions = [{"name": "some_password", "type": "password", "ask": "some question",}] + answers = {} + expected_result = OrderedDict({"some_password": ("some_value", "password")}) + + with patch.object(msignals, "prompt", return_value="some_value"): + assert _parse_args_in_yunohost_format(answers, questions) == expected_result + + +@pytest.mark.skip # that shit should work x( +def test_parse_args_in_yunohost_format_password_input_no_ask(): + questions = [{"name": "some_password", "type": "password",}] + answers = {} + expected_result = OrderedDict({"some_password": ("some_value", "password")}) + + with patch.object(msignals, "prompt", return_value="some_value"): + assert _parse_args_in_yunohost_format(answers, questions) == expected_result + + +def test_parse_args_in_yunohost_format_password_no_input_optional(): + questions = [{"name": "some_password", "type": "password", "optional": True,}] + answers = {} + expected_result = OrderedDict({"some_password": ("", "password")}) + assert _parse_args_in_yunohost_format(answers, questions) == expected_result + + +def test_parse_args_in_yunohost_format_password_optional_with_input(): + questions = [ + { + "name": "some_password", + "ask": "some question", + "type": "password", + "optional": True, + } + ] + answers = {} + expected_result = OrderedDict({"some_password": ("some_value", "password")}) + + with patch.object(msignals, "prompt", return_value="some_value"): + assert _parse_args_in_yunohost_format(answers, questions) == expected_result + + +@pytest.mark.skip # this should work without ask +def test_parse_args_in_yunohost_format_password_optional_with_input_without_ask(): + questions = [{"name": "some_password", "type": "password", "optional": True,}] + answers = {} + expected_result = OrderedDict({"some_password": ("some_value", "password")}) + + with patch.object(msignals, "prompt", return_value="some_value"): + assert _parse_args_in_yunohost_format(answers, questions) == expected_result + + +@pytest.mark.skip # this should raises +def test_parse_args_in_yunohost_format_password_no_input_default(): + questions = [ + { + "name": "some_password", + "type": "password", + "ask": "some question", + "default": "some_value", + } + ] + answers = {} + + # no default for password! + with pytest.raises(YunohostError): + _parse_args_in_yunohost_format(answers, questions) + + +@pytest.mark.skip # this should raises +def test_parse_args_in_yunohost_format_password_no_input_example(): + questions = [ + { + "name": "some_password", + "type": "password", + "ask": "some question", + "example": "some_value", + } + ] + answers = {"some_password": "some_value"} + + # no example for password! + with pytest.raises(YunohostError): + _parse_args_in_yunohost_format(answers, questions) + + +def test_parse_args_in_yunohost_format_password_input_test_ask(): + ask_text = "some question" + questions = [{"name": "some_password", "type": "password", "ask": ask_text,}] + answers = {} + + with patch.object(msignals, "prompt", return_value="some_value") as prompt: + _parse_args_in_yunohost_format(answers, questions) + prompt.assert_called_with(ask_text, True) + + +@pytest.mark.skip # we should do something with this example +def test_parse_args_in_yunohost_format_password_input_test_ask_with_example(): + ask_text = "some question" + example_text = "some example" + questions = [ + { + "name": "some_password", + "type": "password", + "ask": ask_text, + "example": example_text, + } + ] + answers = {} + + with patch.object(msignals, "prompt", return_value="some_value") as prompt: + _parse_args_in_yunohost_format(answers, questions) + assert ask_text in prompt.call_args[0][0] + assert example_text in prompt.call_args[0][0] + + +@pytest.mark.skip # we should do something with this help +def test_parse_args_in_yunohost_format_password_input_test_ask_with_help(): + ask_text = "some question" + help_text = "some_help" + questions = [ + { + "name": "some_password", + "type": "password", + "ask": ask_text, + "help": help_text, + } + ] + answers = {} + + with patch.object(msignals, "prompt", return_value="some_value") as prompt: + _parse_args_in_yunohost_format(answers, questions) + assert ask_text in prompt.call_args[0][0] + assert help_text in prompt.call_args[0][0] + + +def test_parse_args_in_yunohost_format_path(): + questions = [{"name": "some_path", "type": "path",}] + answers = {"some_path": "some_value"} + expected_result = OrderedDict({"some_path": ("some_value", "path")}) + assert _parse_args_in_yunohost_format(answers, questions) == expected_result + + +def test_parse_args_in_yunohost_format_path_no_input(): + questions = [{"name": "some_path", "type": "path",}] + answers = {} + + with pytest.raises(YunohostError): + _parse_args_in_yunohost_format(answers, questions) + + +def test_parse_args_in_yunohost_format_path_input(): + questions = [{"name": "some_path", "type": "path", "ask": "some question",}] + answers = {} + expected_result = OrderedDict({"some_path": ("some_value", "path")}) + + with patch.object(msignals, "prompt", return_value="some_value"): + assert _parse_args_in_yunohost_format(answers, questions) == expected_result + + +@pytest.mark.skip # that shit should work x( +def test_parse_args_in_yunohost_format_path_input_no_ask(): + questions = [{"name": "some_path", "type": "path",}] + answers = {} + expected_result = OrderedDict({"some_path": ("some_value", "path")}) + + with patch.object(msignals, "prompt", return_value="some_value"): + assert _parse_args_in_yunohost_format(answers, questions) == expected_result + + +def test_parse_args_in_yunohost_format_path_no_input_optional(): + questions = [{"name": "some_path", "type": "path", "optional": True,}] + answers = {} + expected_result = OrderedDict({"some_path": ("", "path")}) + assert _parse_args_in_yunohost_format(answers, questions) == expected_result + + +def test_parse_args_in_yunohost_format_path_optional_with_input(): + questions = [ + {"name": "some_path", "ask": "some question", "type": "path", "optional": True,} + ] + answers = {} + expected_result = OrderedDict({"some_path": ("some_value", "path")}) + + with patch.object(msignals, "prompt", return_value="some_value"): + assert _parse_args_in_yunohost_format(answers, questions) == expected_result + + +@pytest.mark.skip # this should work without ask +def test_parse_args_in_yunohost_format_path_optional_with_input_without_ask(): + questions = [{"name": "some_path", "type": "path", "optional": True,}] + answers = {} + expected_result = OrderedDict({"some_path": ("some_value", "path")}) + + with patch.object(msignals, "prompt", return_value="some_value"): + assert _parse_args_in_yunohost_format(answers, questions) == expected_result + + +def test_parse_args_in_yunohost_format_path_no_input_default(): + questions = [ + { + "name": "some_path", + "ask": "some question", + "type": "path", + "default": "some_value", + } + ] + answers = {} + expected_result = OrderedDict({"some_path": ("some_value", "path")}) + assert _parse_args_in_yunohost_format(answers, questions) == expected_result + + +def test_parse_args_in_yunohost_format_path_input_test_ask(): + ask_text = "some question" + questions = [{"name": "some_path", "type": "path", "ask": ask_text,}] + answers = {} + + with patch.object(msignals, "prompt", return_value="some_value") as prompt: + _parse_args_in_yunohost_format(answers, questions) + prompt.assert_called_with(ask_text, False) + + +def test_parse_args_in_yunohost_format_path_input_test_ask_with_default(): + ask_text = "some question" + default_text = "some example" + questions = [ + {"name": "some_path", "type": "path", "ask": ask_text, "default": default_text,} + ] + answers = {} + + with patch.object(msignals, "prompt", return_value="some_value") as prompt: + _parse_args_in_yunohost_format(answers, questions) + prompt.assert_called_with("%s (default: %s)" % (ask_text, default_text), False) + + +@pytest.mark.skip # we should do something with this example +def test_parse_args_in_yunohost_format_path_input_test_ask_with_example(): + ask_text = "some question" + example_text = "some example" + questions = [ + {"name": "some_path", "type": "path", "ask": ask_text, "example": example_text,} + ] + answers = {} + + with patch.object(msignals, "prompt", return_value="some_value") as prompt: + _parse_args_in_yunohost_format(answers, questions) + assert ask_text in prompt.call_args[0][0] + assert example_text in prompt.call_args[0][0] + + +@pytest.mark.skip # we should do something with this help +def test_parse_args_in_yunohost_format_path_input_test_ask_with_help(): + ask_text = "some question" + help_text = "some_help" + questions = [ + {"name": "some_path", "type": "path", "ask": ask_text, "help": help_text,} + ] + answers = {} + + with patch.object(msignals, "prompt", return_value="some_value") as prompt: + _parse_args_in_yunohost_format(answers, questions) + assert ask_text in prompt.call_args[0][0] + assert help_text in prompt.call_args[0][0] + + +def test_parse_args_in_yunohost_format_boolean(): + questions = [{"name": "some_boolean", "type": "boolean",}] + answers = {"some_boolean": "y"} + expected_result = OrderedDict({"some_boolean": (1, "boolean")}) + assert _parse_args_in_yunohost_format(answers, questions) == expected_result + + +def test_parse_args_in_yunohost_format_boolean_all_yes(): + questions = [{"name": "some_boolean", "type": "boolean",}] + expected_result = OrderedDict({"some_boolean": (1, "boolean")}) + assert ( + _parse_args_in_yunohost_format({"some_boolean": "y"}, questions) + == expected_result + ) + assert ( + _parse_args_in_yunohost_format({"some_boolean": "Y"}, questions) + == expected_result + ) + assert ( + _parse_args_in_yunohost_format({"some_boolean": "yes"}, questions) + == expected_result + ) + assert ( + _parse_args_in_yunohost_format({"some_boolean": "Yes"}, questions) + == expected_result + ) + assert ( + _parse_args_in_yunohost_format({"some_boolean": "YES"}, questions) + == expected_result + ) + assert ( + _parse_args_in_yunohost_format({"some_boolean": "1"}, questions) + == expected_result + ) + assert ( + _parse_args_in_yunohost_format({"some_boolean": 1}, questions) + == expected_result + ) + assert ( + _parse_args_in_yunohost_format({"some_boolean": True}, questions) + == expected_result + ) + + +def test_parse_args_in_yunohost_format_boolean_all_no(): + questions = [{"name": "some_boolean", "type": "boolean",}] + expected_result = OrderedDict({"some_boolean": (0, "boolean")}) + assert ( + _parse_args_in_yunohost_format({"some_boolean": "n"}, questions) + == expected_result + ) + assert ( + _parse_args_in_yunohost_format({"some_boolean": "N"}, questions) + == expected_result + ) + assert ( + _parse_args_in_yunohost_format({"some_boolean": "no"}, questions) + == expected_result + ) + assert ( + _parse_args_in_yunohost_format({"some_boolean": "No"}, questions) + == expected_result + ) + assert ( + _parse_args_in_yunohost_format({"some_boolean": "No"}, questions) + == expected_result + ) + assert ( + _parse_args_in_yunohost_format({"some_boolean": "0"}, questions) + == expected_result + ) + assert ( + _parse_args_in_yunohost_format({"some_boolean": 0}, questions) + == expected_result + ) + assert ( + _parse_args_in_yunohost_format({"some_boolean": False}, questions) + == expected_result + ) + + +# XXX apparently boolean are always False (0) by default, I'm not sure what to think about that +def test_parse_args_in_yunohost_format_boolean_no_input(): + questions = [{"name": "some_boolean", "type": "boolean",}] + answers = {} + + expected_result = OrderedDict({"some_boolean": (0, "boolean")}) + assert _parse_args_in_yunohost_format(answers, questions) == expected_result + + +def test_parse_args_in_yunohost_format_boolean_bad_input(): + questions = [{"name": "some_boolean", "type": "boolean",}] + answers = {"some_boolean": "stuff"} + + with pytest.raises(YunohostError): + _parse_args_in_yunohost_format(answers, questions) + + +def test_parse_args_in_yunohost_format_boolean_input(): + questions = [{"name": "some_boolean", "type": "boolean", "ask": "some question",}] + answers = {} + + expected_result = OrderedDict({"some_boolean": (1, "boolean")}) + with patch.object(msignals, "prompt", return_value="y"): + assert _parse_args_in_yunohost_format(answers, questions) == expected_result + + expected_result = OrderedDict({"some_boolean": (0, "boolean")}) + with patch.object(msignals, "prompt", return_value="n"): + assert _parse_args_in_yunohost_format(answers, questions) == expected_result + + +@pytest.mark.skip # we should work +def test_parse_args_in_yunohost_format_boolean_input_no_ask(): + questions = [{"name": "some_boolean", "type": "boolean",}] + answers = {} + expected_result = OrderedDict({"some_boolean": ("some_value", "boolean")}) + + with patch.object(msignals, "prompt", return_value="y"): + assert _parse_args_in_yunohost_format(answers, questions) == expected_result + + +def test_parse_args_in_yunohost_format_boolean_no_input_optional(): + questions = [{"name": "some_boolean", "type": "boolean", "optional": True,}] + answers = {} + expected_result = OrderedDict({"some_boolean": (0, "boolean")}) # default to false + assert _parse_args_in_yunohost_format(answers, questions) == expected_result + + +def test_parse_args_in_yunohost_format_boolean_optional_with_input(): + questions = [ + { + "name": "some_boolean", + "ask": "some question", + "type": "boolean", + "optional": True, + } + ] + answers = {} + expected_result = OrderedDict({"some_boolean": (1, "boolean")}) + + with patch.object(msignals, "prompt", return_value="y"): + assert _parse_args_in_yunohost_format(answers, questions) == expected_result + + +def test_parse_args_in_yunohost_format_boolean_optional_with_input_without_ask(): + questions = [{"name": "some_boolean", "type": "boolean", "optional": True,}] + answers = {} + expected_result = OrderedDict({"some_boolean": (0, "boolean")}) + + with patch.object(msignals, "prompt", return_value="n"): + assert _parse_args_in_yunohost_format(answers, questions) == expected_result + + +def test_parse_args_in_yunohost_format_boolean_no_input_default(): + questions = [ + { + "name": "some_boolean", + "ask": "some question", + "type": "boolean", + "default": 0, + } + ] + answers = {} + expected_result = OrderedDict({"some_boolean": (0, "boolean")}) + assert _parse_args_in_yunohost_format(answers, questions) == expected_result + + +@pytest.mark.skip # we should raise +def test_parse_args_in_yunohost_format_boolean_bad_default(): + questions = [ + { + "name": "some_boolean", + "ask": "some question", + "type": "boolean", + "default": "bad default", + } + ] + answers = {} + with pytest.raises(YunohostError): + _parse_args_in_yunohost_format(answers, questions) + + +def test_parse_args_in_yunohost_format_boolean_input_test_ask(): + ask_text = "some question" + questions = [{"name": "some_boolean", "type": "boolean", "ask": ask_text,}] + answers = {} + + with patch.object(msignals, "prompt", return_value=0) as prompt: + _parse_args_in_yunohost_format(answers, questions) + prompt.assert_called_with(ask_text + " [yes | no] (default: no)", False) + + +def test_parse_args_in_yunohost_format_boolean_input_test_ask_with_default(): + ask_text = "some question" + default_text = 1 + questions = [ + { + "name": "some_boolean", + "type": "boolean", + "ask": ask_text, + "default": default_text, + } + ] + answers = {} + + with patch.object(msignals, "prompt", return_value=1) as prompt: + _parse_args_in_yunohost_format(answers, questions) + prompt.assert_called_with("%s [yes | no] (default: yes)" % ask_text, False) + + +def test_parse_args_in_yunohost_format_domain_empty(): + questions = [{"name": "some_domain", "type": "domain",}] + answers = {} + + with patch.object( + domain, "_get_maindomain", return_value="my_main_domain.com" + ), patch.object( + domain, "domain_list", return_value={"domains": ["my_main_domain.com"]} + ): + with pytest.raises(YunohostError): + _parse_args_in_yunohost_format(answers, questions) + + +def test_parse_args_in_yunohost_format_domain(): + main_domain = "my_main_domain.com" + domains = [main_domain] + questions = [{"name": "some_domain", "type": "domain",}] + + answers = {"some_domain": main_domain} + expected_result = OrderedDict({"some_domain": (main_domain, "domain")}) + + with patch.object( + domain, "_get_maindomain", return_value=main_domain + ), patch.object(domain, "domain_list", return_value={"domains": domains}): + assert _parse_args_in_yunohost_format(answers, questions) == expected_result + + +def test_parse_args_in_yunohost_format_domain_two_domains(): + main_domain = "my_main_domain.com" + other_domain = "some_other_domain.tld" + domains = [main_domain, other_domain] + + questions = [{"name": "some_domain", "type": "domain",}] + answers = {"some_domain": other_domain} + expected_result = OrderedDict({"some_domain": (other_domain, "domain")}) + + with patch.object( + domain, "_get_maindomain", return_value=main_domain + ), patch.object(domain, "domain_list", return_value={"domains": domains}): + assert _parse_args_in_yunohost_format(answers, questions) == expected_result + + answers = {"some_domain": main_domain} + expected_result = OrderedDict({"some_domain": (main_domain, "domain")}) + + with patch.object( + domain, "_get_maindomain", return_value=main_domain + ), patch.object(domain, "domain_list", return_value={"domains": domains}): + assert _parse_args_in_yunohost_format(answers, questions) == expected_result + + +def test_parse_args_in_yunohost_format_domain_two_domains_wrong_answer(): + main_domain = "my_main_domain.com" + other_domain = "some_other_domain.tld" + domains = [main_domain, other_domain] + + questions = [{"name": "some_domain", "type": "domain",}] + answers = {"some_domain": "doesnt_exist.pouet"} + + with patch.object( + domain, "_get_maindomain", return_value=main_domain + ), patch.object(domain, "domain_list", return_value={"domains": domains}): + with pytest.raises(YunohostError): + _parse_args_in_yunohost_format(answers, questions) + + +@pytest.mark.skip # XXX should work +def test_parse_args_in_yunohost_format_domain_two_domains_default_no_ask(): + main_domain = "my_main_domain.com" + other_domain = "some_other_domain.tld" + domains = [main_domain, other_domain] + + questions = [{"name": "some_domain", "type": "domain",}] + answers = {} + expected_result = OrderedDict({"some_domain": (main_domain, "domain")}) + + with patch.object( + domain, "_get_maindomain", return_value=main_domain + ), patch.object(domain, "domain_list", return_value={"domains": domains}): + assert _parse_args_in_yunohost_format(answers, questions) == expected_result + + +def test_parse_args_in_yunohost_format_domain_two_domains_default(): + main_domain = "my_main_domain.com" + other_domain = "some_other_domain.tld" + domains = [main_domain, other_domain] + + questions = [{"name": "some_domain", "type": "domain", "ask": "choose a domain"}] + answers = {} + expected_result = OrderedDict({"some_domain": (main_domain, "domain")}) + + with patch.object( + domain, "_get_maindomain", return_value=main_domain + ), patch.object(domain, "domain_list", return_value={"domains": domains}): + assert _parse_args_in_yunohost_format(answers, questions) == expected_result + + +def test_parse_args_in_yunohost_format_domain_two_domains_default_input(): + main_domain = "my_main_domain.com" + other_domain = "some_other_domain.tld" + domains = [main_domain, other_domain] + + questions = [{"name": "some_domain", "type": "domain", "ask": "choose a domain"}] + answers = {} + + with patch.object( + domain, "_get_maindomain", return_value=main_domain + ), patch.object(domain, "domain_list", return_value={"domains": domains}): + expected_result = OrderedDict({"some_domain": (main_domain, "domain")}) + with patch.object(msignals, "prompt", return_value=main_domain): + assert _parse_args_in_yunohost_format(answers, questions) == expected_result + + expected_result = OrderedDict({"some_domain": (other_domain, "domain")}) + with patch.object(msignals, "prompt", return_value=other_domain): + assert _parse_args_in_yunohost_format(answers, questions) == expected_result + + +def test_parse_args_in_yunohost_format_user_empty(): + users = { + "some_user": { + "ssh_allowed": False, + "username": "some_user", + "shell": "/bin/false", + "mailbox-quota": "0", + "mail": "p@ynh.local", + "fullname": "the first name the last name", + } + } + + questions = [{"name": "some_user", "type": "user",}] + answers = {} + + with patch.object(user, "user_list", return_value={"users": users}): + with pytest.raises(YunohostError): + _parse_args_in_yunohost_format(answers, questions) + + +def test_parse_args_in_yunohost_format_user(): + username = "some_user" + users = { + username: { + "ssh_allowed": False, + "username": "some_user", + "shell": "/bin/false", + "mailbox-quota": "0", + "mail": "p@ynh.local", + "fullname": "the first name the last name", + } + } + + questions = [{"name": "some_user", "type": "user",}] + answers = {"some_user": username} + + expected_result = OrderedDict({"some_user": (username, "user")}) + + with patch.object(user, "user_list", return_value={"users": users}): + assert _parse_args_in_yunohost_format(answers, questions) == expected_result + + +def test_parse_args_in_yunohost_format_user_two_users(): + username = "some_user" + other_user = "some_other_user" + users = { + username: { + "ssh_allowed": False, + "username": "some_user", + "shell": "/bin/false", + "mailbox-quota": "0", + "mail": "p@ynh.local", + "fullname": "the first name the last name", + }, + other_user: { + "ssh_allowed": False, + "username": "some_user", + "shell": "/bin/false", + "mailbox-quota": "0", + "mail": "z@ynh.local", + "fullname": "john doe", + }, + } + + questions = [{"name": "some_user", "type": "user",}] + answers = {"some_user": other_user} + expected_result = OrderedDict({"some_user": (other_user, "user")}) + + with patch.object(user, "user_list", return_value={"users": users}): + assert _parse_args_in_yunohost_format(answers, questions) == expected_result + + answers = {"some_user": username} + expected_result = OrderedDict({"some_user": (username, "user")}) + + with patch.object(user, "user_list", return_value={"users": users}): + assert _parse_args_in_yunohost_format(answers, questions) == expected_result + + +def test_parse_args_in_yunohost_format_user_two_users_wrong_answer(): + username = "my_username.com" + other_user = "some_other_user" + users = { + username: { + "ssh_allowed": False, + "username": "some_user", + "shell": "/bin/false", + "mailbox-quota": "0", + "mail": "p@ynh.local", + "fullname": "the first name the last name", + }, + other_user: { + "ssh_allowed": False, + "username": "some_user", + "shell": "/bin/false", + "mailbox-quota": "0", + "mail": "z@ynh.local", + "fullname": "john doe", + }, + } + + questions = [{"name": "some_user", "type": "user",}] + answers = {"some_user": "doesnt_exist.pouet"} + + with patch.object(user, "user_list", return_value={"users": users}): + with pytest.raises(YunohostError): + _parse_args_in_yunohost_format(answers, questions) + + +def test_parse_args_in_yunohost_format_user_two_users_no_default(): + username = "my_username.com" + other_user = "some_other_user.tld" + users = { + username: { + "ssh_allowed": False, + "username": "some_user", + "shell": "/bin/false", + "mailbox-quota": "0", + "mail": "p@ynh.local", + "fullname": "the first name the last name", + }, + other_user: { + "ssh_allowed": False, + "username": "some_user", + "shell": "/bin/false", + "mailbox-quota": "0", + "mail": "z@ynh.local", + "fullname": "john doe", + }, + } + + questions = [{"name": "some_user", "type": "user", "ask": "choose a user"}] + answers = {} + + with patch.object(user, "user_list", return_value={"users": users}): + with pytest.raises(YunohostError): + _parse_args_in_yunohost_format(answers, questions) + + +def test_parse_args_in_yunohost_format_user_two_users_default_input(): + username = "my_username.com" + other_user = "some_other_user.tld" + users = { + username: { + "ssh_allowed": False, + "username": "some_user", + "shell": "/bin/false", + "mailbox-quota": "0", + "mail": "p@ynh.local", + "fullname": "the first name the last name", + }, + other_user: { + "ssh_allowed": False, + "username": "some_user", + "shell": "/bin/false", + "mailbox-quota": "0", + "mail": "z@ynh.local", + "fullname": "john doe", + }, + } + + questions = [{"name": "some_user", "type": "user", "ask": "choose a user"}] + answers = {} + + with patch.object(user, "user_list", return_value={"users": users}): + expected_result = OrderedDict({"some_user": (username, "user")}) + with patch.object(msignals, "prompt", return_value=username): + assert _parse_args_in_yunohost_format(answers, questions) == expected_result + + expected_result = OrderedDict({"some_user": (other_user, "user")}) + with patch.object(msignals, "prompt", return_value=other_user): + assert _parse_args_in_yunohost_format(answers, questions) == expected_result + + +def test_parse_args_in_yunohost_format_app_empty(): + apps = [ + { + "id": "my_webapp", + "version": "1.0~ynh2", + "description": "Custom Web app with SFTP access", + "name": "Custom Webapp", + } + ] + + questions = [{"name": "some_app", "type": "app",}] + answers = {} + + with patch.object(app, "app_list", return_value={"apps": apps}): + with pytest.raises(YunohostError): + _parse_args_in_yunohost_format(answers, questions) + + +def test_parse_args_in_yunohost_format_app_no_apps(): + apps = [] + questions = [{"name": "some_app", "type": "app",}] + answers = {} + + with patch.object(app, "app_list", return_value={"apps": apps}): + with pytest.raises(YunohostError): + _parse_args_in_yunohost_format(answers, questions) + + +@pytest.mark.skip # XXX should work +def test_parse_args_in_yunohost_format_app_no_apps_optional(): + apps = [] + questions = [{"name": "some_app", "type": "app", "optional": True}] + answers = {} + + with patch.object(app, "app_list", return_value={"apps": apps}): + assert _parse_args_in_yunohost_format(answers, questions) == [] + + +def test_parse_args_in_yunohost_format_app(): + app_name = "my_webapp" + apps = [ + { + "id": app_name, + "version": "1.0~ynh2", + "description": "Custom Web app with SFTP access", + "name": "Custom Webapp", + } + ] + + questions = [{"name": "some_app", "type": "app",}] + answers = {"some_app": app_name} + + expected_result = OrderedDict({"some_app": (app_name, "app")}) + + with patch.object(app, "app_list", return_value={"apps": apps}), patch.object( + app, + "_is_installed", + new_callable=lambda: lambda app_id: app_id in {x["id"] for x in apps}, + ): + assert _parse_args_in_yunohost_format(answers, questions) == expected_result + + +def test_parse_args_in_yunohost_format_app_two_apps(): + app_name = "my_webapp" + other_app = "some_other_app" + apps = [ + { + "id": app_name, + "version": "1.0~ynh2", + "description": "Custom Web app with SFTP access", + "name": "Custom Webapp", + }, + { + "id": other_app, + "version": "1.0", + "description": "blabla", + "name": "stuff", + }, + ] + + questions = [{"name": "some_app", "type": "app",}] + answers = {"some_app": other_app} + expected_result = OrderedDict({"some_app": (other_app, "app")}) + + with patch.object(app, "app_list", return_value={"apps": apps}), patch.object( + app, + "_is_installed", + new_callable=lambda: lambda app_id: app_id in {x["id"] for x in apps}, + ): + assert _parse_args_in_yunohost_format(answers, questions) == expected_result + + answers = {"some_app": app_name} + expected_result = OrderedDict({"some_app": (app_name, "app")}) + + with patch.object(app, "app_list", return_value={"apps": apps}), patch.object( + app, + "_is_installed", + new_callable=lambda: lambda app_id: app_id in {x["id"] for x in apps}, + ): + assert _parse_args_in_yunohost_format(answers, questions) == expected_result + + +def test_parse_args_in_yunohost_format_app_two_apps_wrong_answer(): + app_name = "my_webapp" + other_app = "some_other_app" + apps = [ + { + "id": app_name, + "version": "1.0~ynh2", + "description": "Custom Web app with SFTP access", + "name": "Custom Webapp", + }, + { + "id": other_app, + "version": "1.0", + "description": "blabla", + "name": "stuff", + }, + ] + + questions = [{"name": "some_app", "type": "app",}] + answers = {"some_app": "doesnt_exist"} + + with pytest.raises(YunohostError): + with patch.object(app, "app_list", return_value={"apps": apps}), patch.object( + app, + "_is_installed", + new_callable=lambda: lambda app_id: app_id in {x["id"] for x in apps}, + ): + _parse_args_in_yunohost_format(answers, questions) + + +def test_parse_args_in_yunohost_format_app_two_apps_no_default(): + app_name = "my_app_name.com" + other_app = "some_other_app" + apps = [ + { + "id": app_name, + "version": "1.0~ynh2", + "description": "Custom Web app with SFTP access", + "name": "Custom Webapp", + }, + { + "id": other_app, + "version": "1.0", + "description": "blabla", + "name": "stuff", + }, + ] + + questions = [{"name": "some_app", "type": "app", "ask": "choose a app"}] + answers = {} + + with patch.object(app, "app_list", return_value={"apps": apps}), patch.object( + app, + "_is_installed", + new_callable=lambda: lambda app_id: app_id in {x["id"] for x in apps}, + ): + with pytest.raises(YunohostError): + _parse_args_in_yunohost_format(answers, questions) + + +def test_parse_args_in_yunohost_format_app_two_apps_default_input(): + app_name = "my_app_name.com" + other_app = "some_other_app" + apps = [ + { + "id": app_name, + "version": "1.0~ynh2", + "description": "Custom Web app with SFTP access", + "name": "Custom Webapp", + }, + { + "id": other_app, + "version": "1.0", + "description": "blabla", + "name": "stuff", + }, + ] + + questions = [{"name": "some_app", "type": "app", "ask": "choose a app"}] + answers = {} + + with patch.object(app, "app_list", return_value={"apps": apps}), patch.object( + app, + "_is_installed", + new_callable=lambda: lambda app_id: app_id in {x["id"] for x in apps}, + ): + expected_result = OrderedDict({"some_app": (app_name, "app")}) + with patch.object(msignals, "prompt", return_value=app_name): + assert _parse_args_in_yunohost_format(answers, questions) == expected_result + + expected_result = OrderedDict({"some_app": (other_app, "app")}) + with patch.object(msignals, "prompt", return_value=other_app): + assert _parse_args_in_yunohost_format(answers, questions) == expected_result + + +def test_parse_args_in_yunohost_format_display_text(): + questions = [{"name": "some_app", "type": "display_text", "ask": "foobar"}] + answers = {} + + with patch.object(sys, "stdout", new_callable=StringIO) as stdout: + _parse_args_in_yunohost_format(answers, questions) + assert "foobar" in stdout.getvalue() diff --git a/src/yunohost/tests/test_appurl.py b/src/yunohost/tests/test_appurl.py index e7df12a12..a6d86d040 100644 --- a/src/yunohost/tests/test_appurl.py +++ b/src/yunohost/tests/test_appurl.py @@ -1,4 +1,7 @@ import pytest +import os + +from conftest import get_test_apps_dir from yunohost.utils.error import YunohostError from yunohost.app import app_install, app_remove @@ -43,21 +46,21 @@ def test_urlavailable(): def test_registerurl(): - app_install("./tests/apps/register_url_app_ynh", + app_install(os.path.join(get_test_apps_dir(), "register_url_app_ynh"), args="domain=%s&path=%s" % (maindomain, "/urlregisterapp"), force=True) assert not domain_url_available(maindomain, "/urlregisterapp") # Try installing at same location with pytest.raises(YunohostError): - app_install("./tests/apps/register_url_app_ynh", + app_install(os.path.join(get_test_apps_dir(), "register_url_app_ynh"), args="domain=%s&path=%s" % (maindomain, "/urlregisterapp"), force=True) def test_registerurl_baddomain(): with pytest.raises(YunohostError): - app_install("./tests/apps/register_url_app_ynh", + app_install(os.path.join(get_test_apps_dir(), "register_url_app_ynh"), args="domain=%s&path=%s" % ("yolo.swag", "/urlregisterapp"), force=True) diff --git a/src/yunohost/tests/test_backuprestore.py b/src/yunohost/tests/test_backuprestore.py index d6e9672aa..4b90ba275 100644 --- a/src/yunohost/tests/test_backuprestore.py +++ b/src/yunohost/tests/test_backuprestore.py @@ -3,7 +3,7 @@ import os import shutil import subprocess -from conftest import message, raiseYunohostError +from conftest import message, raiseYunohostError, get_test_apps_dir from yunohost.app import app_install, app_remove, app_ssowatconf from yunohost.app import _is_installed @@ -126,9 +126,9 @@ def app_is_installed(app): def backup_test_dependencies_are_met(): # Dummy test apps (or backup archives) - assert os.path.exists("./tests/apps/backup_wordpress_from_2p4") - assert os.path.exists("./tests/apps/legacy_app_ynh") - assert os.path.exists("./tests/apps/backup_recommended_app_ynh") + assert os.path.exists(os.path.join(get_test_apps_dir(), "backup_wordpress_from_2p4")) + assert os.path.exists(os.path.join(get_test_apps_dir(), "legacy_app_ynh")) + assert os.path.exists(os.path.join(get_test_apps_dir(), "backup_recommended_app_ynh")) return True @@ -184,7 +184,7 @@ def uninstall_test_apps_if_needed(): def install_app(app, path, additionnal_args=""): - app_install("./tests/apps/%s" % app, + app_install(os.path.join(get_test_apps_dir(), app), args="domain=%s&path=%s%s" % (maindomain, path, additionnal_args), force=True) @@ -193,22 +193,22 @@ def add_archive_wordpress_from_2p4(): os.system("mkdir -p /home/yunohost.backup/archives") - os.system("cp ./tests/apps/backup_wordpress_from_2p4/backup.info.json \ - /home/yunohost.backup/archives/backup_wordpress_from_2p4.info.json") + os.system("cp " + os.path.join(get_test_apps_dir(), "backup_wordpress_from_2p4/backup.info.json") + \ + " /home/yunohost.backup/archives/backup_wordpress_from_2p4.info.json") - os.system("cp ./tests/apps/backup_wordpress_from_2p4/backup.tar.gz \ - /home/yunohost.backup/archives/backup_wordpress_from_2p4.tar.gz") + os.system("cp " + os.path.join(get_test_apps_dir(), "backup_wordpress_from_2p4/backup.tar.gz") + \ + " /home/yunohost.backup/archives/backup_wordpress_from_2p4.tar.gz") def add_archive_system_from_2p4(): os.system("mkdir -p /home/yunohost.backup/archives") - os.system("cp ./tests/apps/backup_system_from_2p4/backup.info.json \ - /home/yunohost.backup/archives/backup_system_from_2p4.info.json") + os.system("cp " + os.path.join(get_test_apps_dir(), "backup_system_from_2p4/backup.info.json") + \ + " /home/yunohost.backup/archives/backup_system_from_2p4.info.json") - os.system("cp ./tests/apps/backup_system_from_2p4/backup.tar.gz \ - /home/yunohost.backup/archives/backup_system_from_2p4.tar.gz") + os.system("cp " + os.path.join(get_test_apps_dir(), "backup_system_from_2p4/backup.tar.gz") + \ + " /home/yunohost.backup/archives/backup_system_from_2p4.tar.gz") # # System backup # diff --git a/src/yunohost/tests/test_changeurl.py b/src/yunohost/tests/test_changeurl.py index 8888dd9e9..b1bedc7eb 100644 --- a/src/yunohost/tests/test_changeurl.py +++ b/src/yunohost/tests/test_changeurl.py @@ -1,6 +1,9 @@ import pytest import time import requests +import os + +from conftest import get_test_apps_dir from yunohost.app import app_install, app_change_url, app_remove, app_map from yunohost.domain import _get_maindomain @@ -20,7 +23,7 @@ def teardown_function(function): def install_changeurl_app(path): - app_install("./tests/apps/change_url_app_ynh", + app_install(os.path.join(get_test_apps_dir(), "change_url_app_ynh"), args="domain=%s&path=%s" % (maindomain, path), force=True) diff --git a/src/yunohost/tests/test_permission.py b/src/yunohost/tests/test_permission.py index fc86c8dcc..82a509281 100644 --- a/src/yunohost/tests/test_permission.py +++ b/src/yunohost/tests/test_permission.py @@ -5,7 +5,7 @@ import os import json import shutil -from conftest import message, raiseYunohostError +from conftest import message, raiseYunohostError, get_test_apps_dir from yunohost.app import app_install, app_upgrade, app_remove, app_change_url, app_list, app_map, _installed_apps, APPS_SETTING_PATH, _set_app_settings, _get_app_settings from yunohost.user import user_list, user_create, user_delete, \ @@ -833,7 +833,7 @@ def test_ssowat_conf_show_tile_impossible(): @pytest.mark.other_domains(number=1) def test_permission_app_install(): - app_install("./tests/apps/permissions_app_ynh", + app_install(os.path.join(get_test_apps_dir(), "permissions_app_ynh"), args="domain=%s&domain_2=%s&path=%s&is_public=0&admin=%s" % (maindomain, other_domains[0], "/urlpermissionapp", "alice"), force=True) res = user_permission_list(full=True, full_path=False)['permissions'] @@ -862,7 +862,7 @@ def test_permission_app_install(): @pytest.mark.other_domains(number=1) def test_permission_app_remove(): - app_install("./tests/apps/permissions_app_ynh", + app_install(os.path.join(get_test_apps_dir(), "permissions_app_ynh"), args="domain=%s&domain_2=%s&path=%s&is_public=0&admin=%s" % (maindomain, other_domains[0], "/urlpermissionapp", "alice"), force=True) app_remove("permissions_app") @@ -873,7 +873,7 @@ def test_permission_app_remove(): @pytest.mark.other_domains(number=1) def test_permission_app_change_url(): - app_install("./tests/apps/permissions_app_ynh", + app_install(os.path.join(get_test_apps_dir(), "permissions_app_ynh"), args="domain=%s&domain_2=%s&path=%s&admin=%s" % (maindomain, other_domains[0], "/urlpermissionapp", "alice"), force=True) # FIXME : should rework this test to look for differences in the generated app map / app tiles ... @@ -911,7 +911,7 @@ def test_permission_protection_management_by_helper(): @pytest.mark.other_domains(number=1) def test_permission_app_propagation_on_ssowat(): - app_install("./tests/apps/permissions_app_ynh", + app_install(os.path.join(get_test_apps_dir(), "permissions_app_ynh"), args="domain=%s&domain_2=%s&path=%s&is_public=1&admin=%s" % (maindomain, other_domains[0], "/urlpermissionapp", "alice"), force=True) res = user_permission_list(full=True)['permissions'] @@ -942,7 +942,7 @@ def test_permission_app_propagation_on_ssowat(): @pytest.mark.other_domains(number=1) def test_permission_legacy_app_propagation_on_ssowat(): - app_install("./tests/apps/legacy_app_ynh", + app_install(os.path.join(get_test_apps_dir(), "legacy_app_ynh"), args="domain=%s&domain_2=%s&path=%s" % (maindomain, other_domains[0], "/legacy"), force=True) # App is configured as public by default using the legacy unprotected_uri mechanics diff --git a/tox.ini b/tox.ini index 8d033367b..4598ad3d3 100644 --- a/tox.ini +++ b/tox.ini @@ -10,6 +10,7 @@ deps = pytest >= 4.6.3, < 5.0 pyyaml >= 5.1.2, < 6.0 flake8 >= 3.7.9, < 3.8 + urllib3 commands = pytest {posargs}