mirror of
https://github.com/YunoHost/moulinette.git
synced 2024-09-03 20:06:31 +02:00
Merge remote-tracking branch 'origin/dev' into enh-python3
This commit is contained in:
commit
bd27799283
44 changed files with 607 additions and 495 deletions
17
.travis.yml
17
.travis.yml
|
@ -7,12 +7,19 @@ addons:
|
||||||
- slapd
|
- slapd
|
||||||
|
|
||||||
matrix:
|
matrix:
|
||||||
|
allow_failures:
|
||||||
|
- env: TOXENV=py37-pytest
|
||||||
|
- env: TOXENV=py37-lint
|
||||||
include:
|
include:
|
||||||
- python: 3.5
|
- python: 2.7
|
||||||
env: TOXENV=py35
|
env: TOXENV=py27-pytest
|
||||||
- python: 3.5
|
- python: 2.7
|
||||||
env: TOXENV=lint
|
env: TOXENV=py27-lint
|
||||||
- python: 3.6
|
- python: 3.7
|
||||||
|
env: TOXENV=py37-pytest
|
||||||
|
- python: 3.7
|
||||||
|
env: TOXENV=py37-lint
|
||||||
|
- python: 3.7
|
||||||
env: TOXENV=format-check
|
env: TOXENV=format-check
|
||||||
- python: 3.5
|
- python: 3.5
|
||||||
env: TOXENV=docs
|
env: TOXENV=docs
|
||||||
|
|
140
debian/changelog
vendored
140
debian/changelog
vendored
|
@ -1,3 +1,105 @@
|
||||||
|
moulinette (4.1.0) testing; urgency=low
|
||||||
|
|
||||||
|
- [enh] Simplify interface initialization (Moulinette#245)
|
||||||
|
- [enh] Be able to return a raw HTTP response (Moulinette#255)
|
||||||
|
- [fix] Incorrect locale in some situations (Moulinette/d9fa6c7)
|
||||||
|
- [fix] Prevent installing moulinette 4.1 without Yunohost 4.1 (Moulinette/9609fe1)
|
||||||
|
- [fix] Error messages are not displayed in some situations (Moulinette/2501ecd)
|
||||||
|
- Update translations for French, Spanish, Italian, Portuguese, Czech (Moulinette#256)
|
||||||
|
|
||||||
|
Thanks to all contributors <3 ! (ppr, KaeruCT, Omnia89, roukydesbois, miloskroulik, Aleks, Kay0u, miloskroulik)
|
||||||
|
|
||||||
|
-- Kay0u <pierre@kayou.io> Thu, 03 Dec 2020 16:32:44 +0100
|
||||||
|
|
||||||
|
moulinette (4.0.3) stable; urgency=low
|
||||||
|
|
||||||
|
- Bump version number for stable release
|
||||||
|
|
||||||
|
-- Alexandre Aubin <alex.aubin@mailoo.org> Wed, 29 Jul 2020 17:00:00 +0200
|
||||||
|
|
||||||
|
moulinette (4.0.2~beta) testing; urgency=low
|
||||||
|
|
||||||
|
- Bump version number for beta release
|
||||||
|
|
||||||
|
-- Alexandre Aubin <alex.aubin@mailoo.org> Fri, 19 Jun 2020 15:33:29 +0200
|
||||||
|
|
||||||
|
moulinette (4.0.1~alpha) testing; urgency=low
|
||||||
|
|
||||||
|
- [fix] Get rid of legacy code which breaks postinstall on buster for some reason (ac83b10f)
|
||||||
|
- [fix] Remove legacy Breaks and Replaces (e49a47c7)
|
||||||
|
- [fix] Let's hash the password like we do in core during tests (0c78374e)
|
||||||
|
|
||||||
|
-- Alexandre Aubin <alex.aubin@mailoo.org> Fri, 05 Jun 2020 17:32:35 +0200
|
||||||
|
|
||||||
|
moulinette (3.8.1.3) stable; urgency=low
|
||||||
|
|
||||||
|
- Update authorship/maintainer information (7818f07)
|
||||||
|
- [i18n] Translations updated for Arabic, Catalan, French, Italian
|
||||||
|
|
||||||
|
Thanks to all contributors <3 ! (ButterflyOfFire, É. Gaspar, L. Noferini, ppr, xaloc33)
|
||||||
|
|
||||||
|
-- Alexandre Aubin <alex.aubin@mailoo.org> Mon, 27 Jul 2020 17:15:36 +0200
|
||||||
|
|
||||||
|
moulinette (3.8.1.2) stable; urgency=low
|
||||||
|
|
||||||
|
- [fix] locale parsing in some edge case
|
||||||
|
- [i18n] Translations updated for Chinese (Simplified), Catalan, French, Nepali, Occitan
|
||||||
|
|
||||||
|
Thanks to all contributors ! (Aleks, amirale qt, clecle226, Quentí, xaloc33)
|
||||||
|
|
||||||
|
-- Kay0u <pierre@kayou.io> Fri, 22 May 2020 07:58:19 +0000
|
||||||
|
|
||||||
|
moulinette (3.8.1.1) stable; urgency=low
|
||||||
|
|
||||||
|
Bumping version number for stable release
|
||||||
|
|
||||||
|
-- Kay0u <pierre@kayou.io> Wed, 20 May 2020 18:56:36 +0000
|
||||||
|
|
||||||
|
moulinette (3.8.1) testing; urgency=low
|
||||||
|
|
||||||
|
- [fix] Misc technical ux/debugging fixes (#242, #243, #244, 840f27d2)
|
||||||
|
- [fix] try to autorestart ldap when the server is down (#247)
|
||||||
|
- [i18n] Translations updated for Dutch, Esperanto, French, German, Nepali, Polish
|
||||||
|
|
||||||
|
Thanks to all contributors <3 ! (amirale qt, Bram, É. Gaspar, Kay0u, M. Döring, Zeik0s)
|
||||||
|
|
||||||
|
-- Alexandre Aubin <alex.aubin@mailoo.org> Sat, 09 May 2020 21:09:35 +0200
|
||||||
|
|
||||||
|
moulinette (3.8.0) testing; urgency=low
|
||||||
|
|
||||||
|
# Major stuff
|
||||||
|
|
||||||
|
- Simplify auth mechanism (#216)
|
||||||
|
- Add more tests (#230)
|
||||||
|
- Use Black in Moulinette (#220, 6f5daa0, 54b8cab)
|
||||||
|
|
||||||
|
# Minor technical stuff
|
||||||
|
|
||||||
|
- [fix] Don't display comment if argument is already set (#226)
|
||||||
|
- Don't miserably crash if async running can't read incoming message (06d8c48)
|
||||||
|
- Report the actual error when ldap fails (628ffc9)
|
||||||
|
|
||||||
|
# i18n
|
||||||
|
|
||||||
|
- Improve translations for Swedish, Dutch, Italian, Russian, Polish, Portuguese, Catalan, Spanish, Occitan, Nepali, Esperanto, Basque, Chinese (Simplified), Arabic, German, Hungarian, Greek, Turkish, Bengali (Bangladesh)
|
||||||
|
|
||||||
|
Thanks to all contributors ! (Aleks, Bram, ButterflyOfFire, Filip B., Jeroen F., Josué T., Kay0u, Quentí, Yifei D., amirale qt, decentral1se, Elie G., frju365, Romain R., xaloc33)
|
||||||
|
|
||||||
|
-- Kay0u <pierre@kayou.io> Thu, 09 Apr 2020 20:29:48 +0000
|
||||||
|
|
||||||
|
moulinette (3.7.1.1) stable; urgency=low
|
||||||
|
|
||||||
|
- [fix] Report actual errors when some LDAP operation fails to ease
|
||||||
|
debugging
|
||||||
|
|
||||||
|
-- Alexandre Aubin <alex.aubin@mailoo.org> Fri, 17 Apr 2020 17:00:00 +0000
|
||||||
|
|
||||||
|
moulinette (3.7.1) stable; urgency=low
|
||||||
|
|
||||||
|
- [enh] Lazy loading pytz for performances
|
||||||
|
|
||||||
|
-- Alexandre Aubin <alex.aubin@mailoo.org> Thu, 9 Apr 2020 14:55:00 +0000
|
||||||
|
|
||||||
moulinette (3.7.0.2) stable; urgency=low
|
moulinette (3.7.0.2) stable; urgency=low
|
||||||
|
|
||||||
Bumping version number for stable release
|
Bumping version number for stable release
|
||||||
|
@ -8,7 +110,7 @@ moulinette (3.7.0.1) testing; urgency=low
|
||||||
|
|
||||||
- [fix] Slapd may crash if we try to update the LDAP with no change (moulinette#231)
|
- [fix] Slapd may crash if we try to update the LDAP with no change (moulinette#231)
|
||||||
|
|
||||||
Thanks to all contributors (Josue) <3 !
|
Thanks to all contributors (Josué) <3 !
|
||||||
|
|
||||||
-- Kay0u <pierre@kayou.io> Sun, 15 Mar 2020 16:09:25 +0000
|
-- Kay0u <pierre@kayou.io> Sun, 15 Mar 2020 16:09:25 +0000
|
||||||
|
|
||||||
|
@ -16,21 +118,21 @@ moulinette (3.7.0) testing; urgency=low
|
||||||
|
|
||||||
# ~ Major stuff
|
# ~ Major stuff
|
||||||
|
|
||||||
- [enh] Add group and permission mechanism ([Moulinette#189](https://github.com/YunoHost/moulinette/pull/189)
|
- [enh] Add group and permission mechanism (#189)
|
||||||
- [mod] Be able to customize prompt colors ([Moulinette/808f620](https://github.com/YunoHost/Moulinette/commit/808f620))
|
- [mod] Be able to customize prompt colors (808f620)
|
||||||
- [enh] Support app manifests in toml ([Moulinette#204](https://github.com/YunoHost/moulinette/pull/204), [Moulinette/55515cb](https://github.com/YunoHost/Moulinette/commit/55515cb))
|
- [enh] Support app manifests in toml (#204, 55515cb)
|
||||||
- [enh] Quite a lot of messages improvements, string cleaning, language rework... ([Moulinette/599bec3](https://github.com/YunoHost/Moulinette/commit/599bec3), [Moulinette#208](https://github.com/YunoHost/moulinette/pull/208), [Moulinette#213](https://github.com/YunoHost/moulinette/pull/213), [Moulinette/b7d415d](https://github.com/YunoHost/Moulinette/commit/b7d415d), [Moulinette/a8966b8](https://github.com/YunoHost/Moulinette/commit/a8966b8), [Moulinette/fdf9a71](https://github.com/YunoHost/Moulinette/commit/fdf9a71), [Moulinette/d895ae3](https://github.com/YunoHost/Moulinette/commit/d895ae3), [Moulinette/bdf0a1c](https://github.com/YunoHost/Moulinette/commit/bdf0a1c))
|
- [enh] Quite a lot of messages improvements, string cleaning, language rework... (599bec3, #208, #213, b7d415d, a8966b8, fdf9a71, d895ae3, bdf0a1c)
|
||||||
- [i18n] Improved translations for Catalan, Occitan, French, Arabic, Spanish, German, Norwegian Bokmål
|
- [i18n] Improved translations for Catalan, Occitan, French, Arabic, Spanish, German, Norwegian Bokmål
|
||||||
|
|
||||||
# Smaller or pretty technical fix/enh
|
# Smaller or pretty technical fix/enh
|
||||||
|
|
||||||
- [enh] Preparations for moulinette Python3 migration (Tox, Pytest and unit tests) ([Moulinette#203](https://github.com/YunoHost/moulinette/pull/203), [Moulinette#206](https://github.com/YunoHost/moulinette/pull/206), [Moulinette#207](https://github.com/YunoHost/moulinette/pull/207), [Moulinette#210](https://github.com/YunoHost/moulinette/pull/210), [Moulinette#211](https://github.com/YunoHost/moulinette/pull/211) [Moulinette#212](https://github.com/YunoHost/moulinette/pull/212), [Moulinette/2403ee1](https://github.com/YunoHost/Moulinette/commit/2403ee1), [Moulinette/69b0d49](https://github.com/YunoHost/Moulinette/commit/69b0d49), [Moulinette/49c749c](https://github.com/YunoHost/Moulinette/commit/49c749c), [Moulinette/2c84ee1](https://github.com/YunoHost/Moulinette/commit/2c84ee1), [Moulinette/cef72f7](https://github.com/YunoHost/Moulinette/commit/cef72f7))
|
- [enh] Preparations for moulinette Python3 migration (Tox, Pytest and unit tests) (#203, #206, #207, #210, #211 #212, 2403ee1, 69b0d49, 49c749c, 2c84ee1, cef72f7)
|
||||||
- [enh] Add a write_to_yaml utility similar to write_to_json ([Moulinette/2e2e627](https://github.com/YunoHost/Moulinette/commit/2e2e627))
|
- [enh] Add a write_to_yaml utility similar to write_to_json (2e2e627)
|
||||||
- [enh] Warn the user about long locks ([Moulinette#205](https://github.com/YunoHost/moulinette/pull/205))
|
- [enh] Warn the user about long locks (#205)
|
||||||
- [mod] Tweak stuff about setuptools and moulinette deps? ([Moulinette/b739f27](https://github.com/YunoHost/Moulinette/commit/b739f27), [Moulinette/da00fc9](https://github.com/YunoHost/Moulinette/commit/da00fc9), [Moulinette/d8cbbb0](https://github.com/YunoHost/Moulinette/commit/d8cbbb0))
|
- [mod] Tweak stuff about setuptools and moulinette deps? (b739f27, da00fc9, d8cbbb0)
|
||||||
- [fix] Misc micro bugfixes or improvements ([Moulinette/83d9e77](https://github.com/YunoHost/Moulinette/commit/83d9e77))
|
- [fix] Misc micro bugfixes or improvements (83d9e77)
|
||||||
- [doc] Fix doc building + add doc build tests with Tox ([Moulinette/f1ac5b8](https://github.com/YunoHost/Moulinette/commit/f1ac5b8), [Moulinette/df7d478](https://github.com/YunoHost/Moulinette/commit/df7d478), [Moulinette/74c8f79](https://github.com/YunoHost/Moulinette/commit/74c8f79), [Moulinette/bcf92c7](https://github.com/YunoHost/Moulinette/commit/bcf92c7), [Moulinette/af2c80c](https://github.com/YunoHost/Moulinette/commit/af2c80c), [Moulinette/d52a574](https://github.com/YunoHost/Moulinette/commit/d52a574), [Moulinette/307f660](https://github.com/YunoHost/Moulinette/commit/307f660), [Moulinette/dced104](https://github.com/YunoHost/Moulinette/commit/dced104), [Moulinette/ed3823b](https://github.com/YunoHost/Moulinette/commit/ed3823b))
|
- [doc] Fix doc building + add doc build tests with Tox (f1ac5b8, df7d478, 74c8f79, bcf92c7, af2c80c, d52a574, 307f660, dced104, ed3823b)
|
||||||
- [enh] READMEs improvements ([Moulinette/1541b74](https://github.com/YunoHost/Moulinette/commit/1541b74), [Moulinette/ad1eeef](https://github.com/YunoHost/Moulinette/commit/ad1eeef))
|
- [enh] READMEs improvements (1541b74, ad1eeef)
|
||||||
|
|
||||||
Thanks to all contributors <3 ! (accross all repo: Yunohost, Moulinette, SSOwat, Yunohost-admin) : advocatux, Aksel K., Aleks, Allan N., amirale qt, Armin P., Bram, ButterflyOfFire, Carles S. A., chema o. r., decentral1se, Emmanuel V., Etienne M., Filip B., Geoff M., htsr, Jibec, Josué, Julien J., Kayou, liberodark, ljf, lucaskev, Lukas D., madtibo, Martin D., Mélanie C., nr 458 h, pitfd, ppr, Quentí, sidddy, troll, tufek yamero, xaloc33, yalh76
|
Thanks to all contributors <3 ! (accross all repo: Yunohost, Moulinette, SSOwat, Yunohost-admin) : advocatux, Aksel K., Aleks, Allan N., amirale qt, Armin P., Bram, ButterflyOfFire, Carles S. A., chema o. r., decentral1se, Emmanuel V., Etienne M., Filip B., Geoff M., htsr, Jibec, Josué, Julien J., Kayou, liberodark, ljf, lucaskev, Lukas D., madtibo, Martin D., Mélanie C., nr 458 h, pitfd, ppr, Quentí, sidddy, troll, tufek yamero, xaloc33, yalh76
|
||||||
|
|
||||||
|
@ -80,7 +182,7 @@ moulinette (3.5.1) testing; urgency=low
|
||||||
* [fix] Fix case where stdinfo is not provided in call_async_output (0a300e5)
|
* [fix] Fix case where stdinfo is not provided in call_async_output (0a300e5)
|
||||||
* [i18n] Improve translation for Greek, Hungarian, Polish, Swedish, French, Catalan, Occitan
|
* [i18n] Improve translation for Greek, Hungarian, Polish, Swedish, French, Catalan, Occitan
|
||||||
|
|
||||||
Thanks to all contributors (Aleks, ariasuni, Quenti, ppr, Xaloc) <3 !
|
Thanks to all contributors (Aleks, ariasuni, Quentí, ppr, Xaloc) <3 !
|
||||||
|
|
||||||
-- Alexandre Aubin <alex.aubin@mailoo.org> Wed, 03 Apr 2019 02:25:00 +0000
|
-- Alexandre Aubin <alex.aubin@mailoo.org> Wed, 03 Apr 2019 02:25:00 +0000
|
||||||
|
|
||||||
|
@ -88,7 +190,7 @@ moulinette (3.5.0) testing; urgency=low
|
||||||
|
|
||||||
* [i18n] Improve Russian and Chinese (Mandarin) translations
|
* [i18n] Improve Russian and Chinese (Mandarin) translations
|
||||||
|
|
||||||
Contributors : n3uz, Алексей
|
Contributors : n3uz, Алексей
|
||||||
|
|
||||||
-- Alexandre Aubin <alex.aubin@mailoo.org> Wed, 13 Mar 2019 17:20:00 +0000
|
-- Alexandre Aubin <alex.aubin@mailoo.org> Wed, 13 Mar 2019 17:20:00 +0000
|
||||||
|
|
||||||
|
@ -201,7 +303,7 @@ moulinette (2.7.13) testing; urgency=low
|
||||||
* [i18n] Improve translations for Portugueuse, Occitan
|
* [i18n] Improve translations for Portugueuse, Occitan
|
||||||
* [enh] Add read_yaml util (#161)
|
* [enh] Add read_yaml util (#161)
|
||||||
|
|
||||||
Contributors : Bram, by0ne, Quent-in
|
Contributors : Bram, by0ne, Quentí
|
||||||
|
|
||||||
-- Alexandre Aubin <alex.aubin@mailoo.org> Mon, 28 May 2018 02:55:00 +0000
|
-- Alexandre Aubin <alex.aubin@mailoo.org> Mon, 28 May 2018 02:55:00 +0000
|
||||||
|
|
||||||
|
@ -219,7 +321,7 @@ moulinette (2.7.11) testing; urgency=low
|
||||||
* [fix] Avoid cases where get_cookie returns None
|
* [fix] Avoid cases where get_cookie returns None
|
||||||
* [mod] Improve exception logging in ldap stuff
|
* [mod] Improve exception logging in ldap stuff
|
||||||
|
|
||||||
Thanks to all contributors (pitchum, Bram, ButteflyOfFire, J. Keerl, Matthieu, Jibec, David B, Quenti, bjarkan) <3 !
|
Thanks to all contributors (pitchum, Bram, ButteflyOfFire, J. Keerl, Matthieu, Jibec, David B, Quentí, bjarkan) <3 !
|
||||||
|
|
||||||
-- Alexandre Aubin <alex.aubin@mailoo.org> Tue, 01 May 2018 23:33:59 +0000
|
-- Alexandre Aubin <alex.aubin@mailoo.org> Tue, 01 May 2018 23:33:59 +0000
|
||||||
|
|
||||||
|
@ -291,7 +393,7 @@ moulinette (2.7.0) testing; urgency=low
|
||||||
* [enh] Show description of command in --help (#148)
|
* [enh] Show description of command in --help (#148)
|
||||||
* [i18n] Update French translation (#149)
|
* [i18n] Update French translation (#149)
|
||||||
|
|
||||||
Thanks to all contributors (Bram, Aleks, R. Cabaret) ! <3
|
Thanks to all contributors (Bram, Aleks, R. Cabaret) ! <3
|
||||||
|
|
||||||
-- Alexandre Aubin <alex.aubin@mailoo.org> Mon, 07 Aug 2017 13:04:21 -0400
|
-- Alexandre Aubin <alex.aubin@mailoo.org> Mon, 07 Aug 2017 13:04:21 -0400
|
||||||
|
|
||||||
|
@ -307,9 +409,9 @@ moulinette (2.6.0) testing; urgency=low
|
||||||
|
|
||||||
* [fix] Use ordered dict for the actionmap cache (#136)
|
* [fix] Use ordered dict for the actionmap cache (#136)
|
||||||
* [fix] Show positional arguments first in --help / usage (#138)
|
* [fix] Show positional arguments first in --help / usage (#138)
|
||||||
* Update translations for Portuguese, German, Dutch
|
* Update translations for Portuguese, German, Dutch
|
||||||
|
|
||||||
Thanks to all contributors and translators ! (Trollken, frju, Fabien Gruber, Jeroen Keerl, Aleks)
|
Thanks to all contributors and translators ! (Trollken, frju, Fabien Gruber, Jeroen Keerl, Aleks)
|
||||||
|
|
||||||
-- Alexandre Aubin <alex.aubin@mailoo.org> Mon, 24 Apr 2017 12:44:23 -0400
|
-- Alexandre Aubin <alex.aubin@mailoo.org> Mon, 24 Apr 2017 12:44:23 -0400
|
||||||
|
|
||||||
|
|
14
debian/control
vendored
14
debian/control
vendored
|
@ -1,7 +1,7 @@
|
||||||
Source: moulinette
|
Source: moulinette
|
||||||
Section: python
|
Section: python
|
||||||
Priority: optional
|
Priority: optional
|
||||||
Maintainer: Jérôme Lebleu <jerome.lebleu@mailoo.org>
|
Maintainer: YunoHost Contributors <contrib@yunohost.org>
|
||||||
Build-Depends: debhelper (>= 9), python (>= 2.7), dh-python, python-setuptools, python-psutil, python-all (>= 2.7)
|
Build-Depends: debhelper (>= 9), python (>= 2.7), dh-python, python-setuptools, python-psutil, python-all (>= 2.7)
|
||||||
Standards-Version: 3.9.6
|
Standards-Version: 3.9.6
|
||||||
X-Python-Version: >= 2.7
|
X-Python-Version: >= 2.7
|
||||||
|
@ -18,12 +18,10 @@ Depends: ${misc:Depends}, ${python:Depends},
|
||||||
python-toml,
|
python-toml,
|
||||||
python-psutil,
|
python-psutil,
|
||||||
python-tz
|
python-tz
|
||||||
Replaces: yunohost-cli
|
Breaks: yunohost (<< 4.1)
|
||||||
Breaks: yunohost-cli
|
|
||||||
Description: prototype interfaces with ease in Python
|
Description: prototype interfaces with ease in Python
|
||||||
The moulinette is a Python package that allows one to quickly and
|
Quickly and easily prototype interfaces for your application.
|
||||||
easily prototype interfaces for your application. Each action can
|
Each action can be served through an HTTP API and from the
|
||||||
be served through an HTTP API and from the command-line with a single
|
command-line with a single method.
|
||||||
method.
|
|
||||||
.
|
.
|
||||||
It was originally written for the YunoHost project.
|
Originally designed and written for the YunoHost project.
|
||||||
|
|
|
@ -18,7 +18,6 @@
|
||||||
"invalid_password": "كلمة السر خاطئة",
|
"invalid_password": "كلمة السر خاطئة",
|
||||||
"invalid_usage": "إستعمال غير صالح، إستخدم --help لعرض المساعدة",
|
"invalid_usage": "إستعمال غير صالح، إستخدم --help لعرض المساعدة",
|
||||||
"ldap_attribute_already_exists": "الخاصية '{attribute}' موجودة مسبقا و تحمل القيمة '{value}'",
|
"ldap_attribute_already_exists": "الخاصية '{attribute}' موجودة مسبقا و تحمل القيمة '{value}'",
|
||||||
"ldap_operation_error": "طرأ هناك خطأ أثناء عملية في LDAP",
|
|
||||||
"ldap_server_down": "لا يمكن الإتصال بخادم LDAP",
|
"ldap_server_down": "لا يمكن الإتصال بخادم LDAP",
|
||||||
"logged_in": "مُتّصل",
|
"logged_in": "مُتّصل",
|
||||||
"logged_out": "تم تسجيل خروجك",
|
"logged_out": "تم تسجيل خروجك",
|
||||||
|
@ -53,6 +52,9 @@
|
||||||
"corrupted_yaml": "قراءة مُشوّهة لنسق yaml مِن {ressource:s} (السبب : {error:s})",
|
"corrupted_yaml": "قراءة مُشوّهة لنسق yaml مِن {ressource:s} (السبب : {error:s})",
|
||||||
"info": "معلومة:",
|
"info": "معلومة:",
|
||||||
"warn_the_user_about_waiting_lock_again": "جارٍ الانتظار…",
|
"warn_the_user_about_waiting_lock_again": "جارٍ الانتظار…",
|
||||||
"warn_the_user_that_lock_is_acquired": "لقد انتهى تنفيذ ذاك الأمر ، جارٍ إطلاق الأمر",
|
"warn_the_user_that_lock_is_acquired": "لقد انتهى تنفيذ ذاك الأمر للتوّ ، جارٍ تنفيذ هذا الأمر",
|
||||||
"warn_the_user_about_waiting_lock": "هناك أمر لـ YunoHost قيد التشغيل حاليا. في انتظار انتهاء تنفيذه قبل تشغيل التالي"
|
"warn_the_user_about_waiting_lock": "هناك أمر لـ YunoHost قيد التشغيل حاليا. في انتظار انتهاء تنفيذه قبل تشغيل التالي",
|
||||||
|
"ldap_server_is_down_restart_it": "إنّ خدمة LDAP غير مشغّلة ، نحن بصدد محاولة إعادة تشغيلها…",
|
||||||
|
"session_expired": "لقد انتهت مدة صلاحية الجلسة. رجاءً أعد الإستيثاق.",
|
||||||
|
"invalid_token": "إنّ الرمز المميز غير صالح - يرجى الإستيثاق"
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,7 +18,6 @@
|
||||||
"invalid_password": "Contrasenya invàlida",
|
"invalid_password": "Contrasenya invàlida",
|
||||||
"invalid_usage": "Utilització invàlida, utilitzeu --help per veure l'ajuda",
|
"invalid_usage": "Utilització invàlida, utilitzeu --help per veure l'ajuda",
|
||||||
"ldap_attribute_already_exists": "L'atribut '{attribute}' ja existeix amb el valor '{value}'",
|
"ldap_attribute_already_exists": "L'atribut '{attribute}' ja existeix amb el valor '{value}'",
|
||||||
"ldap_operation_error": "Hi ha hagut un error durant l'operació de LDAP",
|
|
||||||
"ldap_server_down": "No s'ha pogut connectar amb el servidor LDAP",
|
"ldap_server_down": "No s'ha pogut connectar amb el servidor LDAP",
|
||||||
"logged_in": "Sessió iniciada",
|
"logged_in": "Sessió iniciada",
|
||||||
"logged_out": "Sessió tancada",
|
"logged_out": "Sessió tancada",
|
||||||
|
@ -40,21 +39,23 @@
|
||||||
"cannot_open_file": "No s'ha pogut obrir el fitxer {file:s} (motiu: {error:s})",
|
"cannot_open_file": "No s'ha pogut obrir el fitxer {file:s} (motiu: {error:s})",
|
||||||
"cannot_write_file": "No s'ha pogut escriure el fitxer {file:s} (motiu: {error:s})",
|
"cannot_write_file": "No s'ha pogut escriure el fitxer {file:s} (motiu: {error:s})",
|
||||||
"unknown_error_reading_file": "Error desconegut al intentar llegir el fitxer {file:s} (motiu: {error:s})",
|
"unknown_error_reading_file": "Error desconegut al intentar llegir el fitxer {file:s} (motiu: {error:s})",
|
||||||
"corrupted_json": "Json corrupte llegit des de {ressource:s} (motiu: {error:s})",
|
"corrupted_json": "JSON corrupte llegit des de {ressource:s} (motiu: {error:s})",
|
||||||
"corrupted_yaml": "Yaml corrupte llegit des de {ressource:s} (motiu: {error:s})",
|
"corrupted_yaml": "YAML corrupte llegit des de {ressource:s} (motiu: {error:s})",
|
||||||
"error_writing_file": "Error al escriure el fitxer {file:s}: {error:s}",
|
"error_writing_file": "Error al escriure el fitxer {file:s}: {error:s}",
|
||||||
"error_removing": "Error al eliminar {path:s}: {error:s}",
|
"error_removing": "Error al eliminar {path:s}: {error:s}",
|
||||||
"error_changing_file_permissions": "Error al canviar els permisos per {path:s}: {error:s}",
|
"error_changing_file_permissions": "Error al canviar els permisos per {path:s}: {error:s}",
|
||||||
"invalid_url": "Url invàlid {url:s} (el lloc web existeix?)",
|
"invalid_url": "URL invàlid {url:s} (el lloc web existeix?)",
|
||||||
"download_ssl_error": "Error SSL al connectar amb {url:s}",
|
"download_ssl_error": "Error SSL al connectar amb {url:s}",
|
||||||
"download_timeout": "{url:s} ha tardat massa en respondre, s'ha deixat d'esperar.",
|
"download_timeout": "{url:s} ha tardat massa en respondre, s'ha deixat d'esperar.",
|
||||||
"download_unknown_error": "Error al baixar dades des de {url:s}: {error:s}",
|
"download_unknown_error": "Error al baixar dades des de {url:s}: {error:s}",
|
||||||
"download_bad_status_code": "{url:s} ha retornat el codi d'estat {code:s}",
|
"download_bad_status_code": "{url:s} ha retornat el codi d'estat {code:s}",
|
||||||
"command_unknown": "Ordre '{command:s}' desconegut ?",
|
"command_unknown": "Ordre '{command:s}' desconegut?",
|
||||||
"info": "Info:",
|
"info": "Info:",
|
||||||
"corrupted_toml": "El fitxer TOML ha estat corromput en la lectura des de {ressource:s} (motiu: {error:s})",
|
"corrupted_toml": "El fitxer TOML ha estat corromput en la lectura des de {ressource:s} (motiu: {error:s})",
|
||||||
"warn_the_user_about_waiting_lock": "Hi ha una altra ordre de YunoHost en execució, s'executarà aquesta ordre un cop l'anterior hagi acabat",
|
"warn_the_user_about_waiting_lock": "Hi ha una altra ordre de YunoHost en execució, s'executarà aquesta ordre un cop l'anterior hagi acabat",
|
||||||
"warn_the_user_about_waiting_lock_again": "Encara en espera…",
|
"warn_the_user_about_waiting_lock_again": "Encara en espera…",
|
||||||
"warn_the_user_that_lock_is_acquired": "l'altra ordre tot just ha acabat, ara s'executarà aquesta ordre",
|
"warn_the_user_that_lock_is_acquired": "L'altra ordre tot just ha acabat, ara s'executarà aquesta ordre",
|
||||||
"invalid_token": "Testimoni no vàlid - torneu-vos a autenticar"
|
"invalid_token": "Testimoni no vàlid - torneu-vos a autenticar",
|
||||||
|
"ldap_server_is_down_restart_it": "El servei LDAP està caigut, s'està intentant tornar-lo a engegar…",
|
||||||
|
"session_expired": "La sessió a expirat. Torneu-vos a autenticar."
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,14 +14,13 @@
|
||||||
"folder_exists": "目录已存在:{path}",
|
"folder_exists": "目录已存在:{path}",
|
||||||
"folder_not_exist": "目录不存在",
|
"folder_not_exist": "目录不存在",
|
||||||
"info": "信息:",
|
"info": "信息:",
|
||||||
"instance_already_running": "实例已正在运行",
|
"instance_already_running": "已经有一个YunoHost操作正在运行。 请等待它完成再运行另一个。",
|
||||||
"invalid_argument": "参数错误{argument}:{error}",
|
"invalid_argument": "参数错误{argument}:{error}",
|
||||||
"invalid_password": "密码错误",
|
"invalid_password": "密码错误",
|
||||||
"invalid_usage": "用法错误,输入 --help 查看帮助信息",
|
"invalid_usage": "用法错误,输入 --help 查看帮助信息",
|
||||||
"ldap_attribute_already_exists": "参数{attribute}已赋值{value}",
|
"ldap_attribute_already_exists": "参数{attribute}已赋值{value}",
|
||||||
"ldap_operation_error": "LDAP操作时发生了错误",
|
|
||||||
"ldap_server_down": "无法连接LDAP服务器",
|
"ldap_server_down": "无法连接LDAP服务器",
|
||||||
"logged_in": "登录成功",
|
"logged_in": "登录",
|
||||||
"logged_out": "登出",
|
"logged_out": "登出",
|
||||||
"not_logged_in": "您未登录",
|
"not_logged_in": "您未登录",
|
||||||
"operation_interrupted": "操作中断",
|
"operation_interrupted": "操作中断",
|
||||||
|
@ -31,7 +30,7 @@
|
||||||
"server_already_running": "服务已运行在指定端口",
|
"server_already_running": "服务已运行在指定端口",
|
||||||
"success": "成功!",
|
"success": "成功!",
|
||||||
"unable_authenticate": "认证失败",
|
"unable_authenticate": "认证失败",
|
||||||
"unable_retrieve_session": "获取会话失败",
|
"unable_retrieve_session": "由于“ {exception}”,无法检索会话",
|
||||||
"unknown_group": "未知组{group}",
|
"unknown_group": "未知组{group}",
|
||||||
"unknown_user": "未知用户{user}",
|
"unknown_user": "未知用户{user}",
|
||||||
"values_mismatch": "值不匹配",
|
"values_mismatch": "值不匹配",
|
||||||
|
@ -39,16 +38,23 @@
|
||||||
"websocket_request_expected": "期望一个WebSocket请求",
|
"websocket_request_expected": "期望一个WebSocket请求",
|
||||||
"cannot_open_file": "不能打开文件{file:s}(原因:{error:s})",
|
"cannot_open_file": "不能打开文件{file:s}(原因:{error:s})",
|
||||||
"cannot_write_file": "写入文件{file:s}失败(原因:{error:s})",
|
"cannot_write_file": "写入文件{file:s}失败(原因:{error:s})",
|
||||||
"unknown_error_reading_file": "尝试读取文件{file:s}时发生错误",
|
"unknown_error_reading_file": "尝试读取文件{files}时发生未知错误(原因:{errors})",
|
||||||
"corrupted_json": "json数据{ressource:s}读取失败(原因:{error:s})",
|
"corrupted_json": "从{ressource:s}读取的JSON损坏(原因:{error:s})",
|
||||||
"corrupted_yaml": "读取yaml文件{ressource:s}失败(原因:{error:s})",
|
"corrupted_yaml": "从{ressource:s}读取的YMAL损坏(原因:{error:s})",
|
||||||
"error_writing_file": "写入文件{file:s}失败:{error:s}",
|
"error_writing_file": "写入文件{file:s}失败:{error:s}",
|
||||||
"error_removing": "删除路径{path:s}失败:{error:s}",
|
"error_removing": "删除路径{path:s}失败:{error:s}",
|
||||||
"error_changing_file_permissions": "目录{path:s}权限修改失败:{error:s}",
|
"error_changing_file_permissions": "目录{path:s}权限修改失败:{error:s}",
|
||||||
"invalid_url": "url:{url:s}无效(site是否存在?)",
|
"invalid_url": "URL:{url:s}无效(site是否存在?)",
|
||||||
"download_ssl_error": "连接{url:s}时发生SSL错误",
|
"download_ssl_error": "连接{url:s}时发生SSL错误",
|
||||||
"download_timeout": "{url:s}响应超时,放弃。",
|
"download_timeout": "{url:s}响应超时,放弃。",
|
||||||
"download_unknown_error": "下载{url:s}失败:{error:s}",
|
"download_unknown_error": "下载{url:s}失败:{error:s}",
|
||||||
"download_bad_status_code": "{url:s}返回状态码:{code:s}",
|
"download_bad_status_code": "{url:s}返回状态码:{code:s}",
|
||||||
"command_unknown": "未知命令:{command:s}?"
|
"command_unknown": "命令'{command:s}'未知?",
|
||||||
|
"warn_the_user_that_lock_is_acquired": "另一个命令刚刚完成,现在启动此命令",
|
||||||
|
"warn_the_user_about_waiting_lock_again": "还在等...",
|
||||||
|
"warn_the_user_about_waiting_lock": "目前正在运行另一个YunoHost命令,我们在运行此命令之前等待它完成",
|
||||||
|
"corrupted_toml": "从{ressource:s}读取的TOML损坏(原因:{error:s})",
|
||||||
|
"invalid_token": "令牌无效-请进行身份验证",
|
||||||
|
"ldap_server_is_down_restart_it": "LDAP服务已下线,正在尝试重启服务……",
|
||||||
|
"session_expired": "会话已过期。请重新进行身份验证。"
|
||||||
}
|
}
|
||||||
|
|
3
locales/cs.json
Normal file
3
locales/cs.json
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
{
|
||||||
|
"password": "Heslo"
|
||||||
|
}
|
|
@ -16,7 +16,6 @@
|
||||||
"invalid_password": "Passwort falsch",
|
"invalid_password": "Passwort falsch",
|
||||||
"invalid_usage": "Falscher Aufruf, verwende --help für den Hilfstext",
|
"invalid_usage": "Falscher Aufruf, verwende --help für den Hilfstext",
|
||||||
"ldap_attribute_already_exists": "Attribute existieren bereits: '{attribute}={value}'",
|
"ldap_attribute_already_exists": "Attribute existieren bereits: '{attribute}={value}'",
|
||||||
"ldap_operation_error": "Ein Fehler trat während der LDAP Abfrage auf",
|
|
||||||
"ldap_server_down": "LDAP-Server nicht erreichbar",
|
"ldap_server_down": "LDAP-Server nicht erreichbar",
|
||||||
"logged_in": "Angemeldet",
|
"logged_in": "Angemeldet",
|
||||||
"logged_out": "Abgemeldet",
|
"logged_out": "Abgemeldet",
|
||||||
|
@ -37,5 +36,26 @@
|
||||||
"deprecated_command_alias": "'{prog} {old}' ist veraltet und wird bald entfernt werden, benutze '{prog} {new}' stattdessen",
|
"deprecated_command_alias": "'{prog} {old}' ist veraltet und wird bald entfernt werden, benutze '{prog} {new}' stattdessen",
|
||||||
"unknown_group": "Gruppe '{group}' ist unbekannt",
|
"unknown_group": "Gruppe '{group}' ist unbekannt",
|
||||||
"unknown_user": "Benutzer '{user}' ist unbekannt",
|
"unknown_user": "Benutzer '{user}' ist unbekannt",
|
||||||
"info": "Info:"
|
"info": "Info:",
|
||||||
|
"invalid_token": "Ungültiger Token - bitte authentifizieren",
|
||||||
|
"corrupted_json": "Beschädigtes JSON gelesen von {ressource:s} (reason: {error:s})",
|
||||||
|
"unknown_error_reading_file": "Unbekannter Fehler beim Lesen der Datei {file:s} (reason: {error:s})",
|
||||||
|
"cannot_write_file": "Kann Datei {file:s} nicht schreiben (reason: {error:s})",
|
||||||
|
"cannot_open_file": "Kann Datei {file:s} nicht öffnen (reason: {error:s})",
|
||||||
|
"corrupted_yaml": "Beschädigtes YAML gelesen von {ressource:s} (reason: {error:s})",
|
||||||
|
"warn_the_user_that_lock_is_acquired": "Der andere Befehl wurde gerade abgeschlossen, starte jetzt diesen Befehl",
|
||||||
|
"warn_the_user_about_waiting_lock_again": "Immer noch wartend...",
|
||||||
|
"warn_the_user_about_waiting_lock": "Ein anderer YunoHost Befehl läuft gerade, wir warten bis er fertig ist, bevor dieser laufen kann",
|
||||||
|
"command_unknown": "Befehl '{command:s}' unbekannt?",
|
||||||
|
"download_bad_status_code": "{url:s} lieferte folgende(n) Status Code(s) {code:s}",
|
||||||
|
"download_unknown_error": "Fehler beim Herunterladen von Daten von {url:s}: {error:s}",
|
||||||
|
"download_timeout": "{url:s} brauchte zu lange zum Antworten, hab aufgegeben.",
|
||||||
|
"download_ssl_error": "SSL Fehler beim Verbinden zu {url:s}",
|
||||||
|
"invalid_url": "Ungültige URL {url:s} (existiert diese Seite?)",
|
||||||
|
"error_changing_file_permissions": "Fehler beim Ändern der Berechtigungen für {path:s}: {error:s}",
|
||||||
|
"error_removing": "Fehler beim Entfernen {path:s}: {error:s}",
|
||||||
|
"error_writing_file": "Fehler beim Schreiben von Datei {file:s}: {error:s}",
|
||||||
|
"corrupted_toml": "Beschädigtes TOML gelesen von {ressource:s} (reason: {error:s})",
|
||||||
|
"ldap_server_is_down_restart_it": "Der LDAP-Dienst wurde angehalten. Es wird versucht, ihn erneut zu starten...",
|
||||||
|
"session_expired": "Die Sitzung ist abgelaufen. Bitte neuauthentifizieren."
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,7 +19,6 @@
|
||||||
"invalid_token": "Invalid token - please authenticate",
|
"invalid_token": "Invalid token - please authenticate",
|
||||||
"invalid_usage": "Invalid usage, pass --help to see help",
|
"invalid_usage": "Invalid usage, pass --help to see help",
|
||||||
"ldap_attribute_already_exists": "Attribute '{attribute}' already exists with value '{value}'",
|
"ldap_attribute_already_exists": "Attribute '{attribute}' already exists with value '{value}'",
|
||||||
"ldap_operation_error": "An error occurred during LDAP '{action}' operation",
|
|
||||||
"ldap_server_down": "Unable to reach LDAP server",
|
"ldap_server_down": "Unable to reach LDAP server",
|
||||||
"logged_in": "Logged in",
|
"logged_in": "Logged in",
|
||||||
"logged_out": "Logged out",
|
"logged_out": "Logged out",
|
||||||
|
@ -32,6 +31,7 @@
|
||||||
"success": "Success!",
|
"success": "Success!",
|
||||||
"unable_authenticate": "Unable to authenticate",
|
"unable_authenticate": "Unable to authenticate",
|
||||||
"unable_retrieve_session": "Unable to retrieve the session because '{exception}'",
|
"unable_retrieve_session": "Unable to retrieve the session because '{exception}'",
|
||||||
|
"session_expired": "The session expired. Please re-authenticate.",
|
||||||
"unknown_group": "Unknown '{group}' group",
|
"unknown_group": "Unknown '{group}' group",
|
||||||
"unknown_user": "Unknown '{user}' user",
|
"unknown_user": "Unknown '{user}' user",
|
||||||
"values_mismatch": "Values don't match",
|
"values_mismatch": "Values don't match",
|
||||||
|
@ -41,19 +41,20 @@
|
||||||
"cannot_open_file": "Could not open file {file:s} (reason: {error:s})",
|
"cannot_open_file": "Could not open file {file:s} (reason: {error:s})",
|
||||||
"cannot_write_file": "Could not write file {file:s} (reason: {error:s})",
|
"cannot_write_file": "Could not write file {file:s} (reason: {error:s})",
|
||||||
"unknown_error_reading_file": "Unknown error while trying to read file {file:s} (reason: {error:s})",
|
"unknown_error_reading_file": "Unknown error while trying to read file {file:s} (reason: {error:s})",
|
||||||
"corrupted_json": "Corrupted json read from {ressource:s} (reason: {error:s})",
|
"corrupted_json": "Corrupted JSON read from {ressource:s} (reason: {error:s})",
|
||||||
"corrupted_yaml": "Corrupted yaml read from {ressource:s} (reason: {error:s})",
|
"corrupted_yaml": "Corrupted YAML read from {ressource:s} (reason: {error:s})",
|
||||||
"corrupted_toml": "Corrupted toml read from {ressource:s} (reason: {error:s})",
|
"corrupted_toml": "Corrupted TOML read from {ressource:s} (reason: {error:s})",
|
||||||
"error_writing_file": "Error when writing file {file:s}: {error:s}",
|
"error_writing_file": "Error when writing file {file:s}: {error:s}",
|
||||||
"error_removing": "Error when removing {path:s}: {error:s}",
|
"error_removing": "Error when removing {path:s}: {error:s}",
|
||||||
"error_changing_file_permissions": "Error when changing permissions for {path:s}: {error:s}",
|
"error_changing_file_permissions": "Error when changing permissions for {path:s}: {error:s}",
|
||||||
"invalid_url": "Invalid url {url:s} (does this site exists?)",
|
"invalid_url": "Invalid URL {url:s} (does this site exists?)",
|
||||||
"download_ssl_error": "SSL error when connecting to {url:s}",
|
"download_ssl_error": "SSL error when connecting to {url:s}",
|
||||||
"download_timeout": "{url:s} took too long to answer, gave up.",
|
"download_timeout": "{url:s} took too long to answer, gave up.",
|
||||||
"download_unknown_error": "Error when downloading data from {url:s}: {error:s}",
|
"download_unknown_error": "Error when downloading data from {url:s}: {error:s}",
|
||||||
"download_bad_status_code": "{url:s} returned status code {code:s}",
|
"download_bad_status_code": "{url:s} returned status code {code:s}",
|
||||||
"command_unknown": "Command '{command:s}' unknown ?",
|
"command_unknown": "Command '{command:s}' unknown?",
|
||||||
"warn_the_user_about_waiting_lock": "Another YunoHost command is running right now, we are waiting for it to finish before running this one",
|
"warn_the_user_about_waiting_lock": "Another YunoHost command is running right now, we are waiting for it to finish before running this one",
|
||||||
"warn_the_user_about_waiting_lock_again": "Still waiting...",
|
"warn_the_user_about_waiting_lock_again": "Still waiting...",
|
||||||
"warn_the_user_that_lock_is_acquired": "the other command just completed, now starting this command"
|
"warn_the_user_that_lock_is_acquired": "The other command just completed, now starting this command",
|
||||||
|
"ldap_server_is_down_restart_it": "The LDAP service is down, attempt to restart it..."
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"password": "Pasvorto",
|
"password": "Pasvorto",
|
||||||
"colon": "{}: ",
|
"colon": "{}: ",
|
||||||
"warn_the_user_that_lock_is_acquired": "la alia komando nur kompletigas, nun komencante ĉi tiun komandon",
|
"warn_the_user_that_lock_is_acquired": "la alia komando ĵus kompletigis, nun komencante ĉi tiun komandon",
|
||||||
"warn_the_user_about_waiting_lock_again": "Ankoraŭ atendanta...",
|
"warn_the_user_about_waiting_lock_again": "Ankoraŭ atendanta...",
|
||||||
"warn_the_user_about_waiting_lock": "Alia komando de YunoHost funkcias ĝuste nun, ni atendas, ke ĝi finiĝos antaŭ ol funkcii ĉi tiu",
|
"warn_the_user_about_waiting_lock": "Alia komando de YunoHost funkcias ĝuste nun, ni atendas, ke ĝi finiĝos antaŭ ol funkcii ĉi tiu",
|
||||||
"command_unknown": "Komando '{command:s}' nekonata?",
|
"command_unknown": "Komando '{command:s}' nekonata?",
|
||||||
|
@ -34,7 +34,6 @@
|
||||||
"not_logged_in": "Vi ne estas ensalutinta",
|
"not_logged_in": "Vi ne estas ensalutinta",
|
||||||
"logged_in": "Ensalutinta",
|
"logged_in": "Ensalutinta",
|
||||||
"ldap_server_down": "Ne eblas atingi la servilon LDAP",
|
"ldap_server_down": "Ne eblas atingi la servilon LDAP",
|
||||||
"ldap_operation_error": "Eraro okazis dum LDAP-operacio",
|
|
||||||
"ldap_attribute_already_exists": "Atributo '{attribute}' jam ekzistas kun valoro '{value}'",
|
"ldap_attribute_already_exists": "Atributo '{attribute}' jam ekzistas kun valoro '{value}'",
|
||||||
"invalid_usage": "Nevalida uzado, preterpase '--help' por vidi helpon",
|
"invalid_usage": "Nevalida uzado, preterpase '--help' por vidi helpon",
|
||||||
"invalid_password": "Nevalida pasvorto",
|
"invalid_password": "Nevalida pasvorto",
|
||||||
|
|
|
@ -18,7 +18,6 @@
|
||||||
"invalid_password": "Contraseña no válida",
|
"invalid_password": "Contraseña no válida",
|
||||||
"invalid_usage": "Uso no válido, utilice --help para ver la ayuda",
|
"invalid_usage": "Uso no válido, utilice --help para ver la ayuda",
|
||||||
"ldap_attribute_already_exists": "El atributo «{attribute}» ya existe con el valor «{value}»",
|
"ldap_attribute_already_exists": "El atributo «{attribute}» ya existe con el valor «{value}»",
|
||||||
"ldap_operation_error": "Ha ocurrido un error durante la operación de LDAP",
|
|
||||||
"ldap_server_down": "No se pudo conectar con el servidor LDAP",
|
"ldap_server_down": "No se pudo conectar con el servidor LDAP",
|
||||||
"logged_in": "Sesión iniciada",
|
"logged_in": "Sesión iniciada",
|
||||||
"logged_out": "Sesión cerrada",
|
"logged_out": "Sesión cerrada",
|
||||||
|
@ -40,21 +39,23 @@
|
||||||
"cannot_open_file": "No se pudo abrir el archivo {file:s} (motivo: {error:s})",
|
"cannot_open_file": "No se pudo abrir el archivo {file:s} (motivo: {error:s})",
|
||||||
"cannot_write_file": "No se pudo escribir el archivo {file:s} (motivo: {error:s})",
|
"cannot_write_file": "No se pudo escribir el archivo {file:s} (motivo: {error:s})",
|
||||||
"unknown_error_reading_file": "Error desconocido al intentar leer el archivo {file:s} (motivo: {error:s})",
|
"unknown_error_reading_file": "Error desconocido al intentar leer el archivo {file:s} (motivo: {error:s})",
|
||||||
"corrupted_json": "Lectura corrupta de Json desde {ressource:s} (motivo: {error:s})",
|
"corrupted_json": "Lectura corrupta de JSON desde {ressource:s} (motivo: {error:s})",
|
||||||
"error_writing_file": "Error al escribir el archivo {file:s}: {error:s}",
|
"error_writing_file": "Error al escribir el archivo {file:s}: {error:s}",
|
||||||
"error_removing": "Error al eliminar {path:s}: {error:s}",
|
"error_removing": "Error al eliminar {path:s}: {error:s}",
|
||||||
"error_changing_file_permissions": "Error al cambiar los permisos para {path:s}: {error:s}",
|
"error_changing_file_permissions": "Error al cambiar los permisos para {path:s}: {error:s}",
|
||||||
"invalid_url": "Url no válida {url:s} (¿Existe este sitio?)",
|
"invalid_url": "URL inválida {url:s} (¿Existe este sitio?)",
|
||||||
"download_ssl_error": "Error SSL al conectar con {url:s}",
|
"download_ssl_error": "Error SSL al conectar con {url:s}",
|
||||||
"download_timeout": "{url:s} tardó demasiado en responder, abandono.",
|
"download_timeout": "{url:s} tardó demasiado en responder, abandono.",
|
||||||
"download_unknown_error": "Error al descargar datos desde {url:s} : {error:s}",
|
"download_unknown_error": "Error al descargar datos desde {url:s} : {error:s}",
|
||||||
"download_bad_status_code": "{url:s} devolvió el código de estado {code:s}",
|
"download_bad_status_code": "{url:s} devolvió el código de estado {code:s}",
|
||||||
"command_unknown": "¿Orden «{command:s}» desconocida?",
|
"command_unknown": "¿Orden «{command:s}» desconocida?",
|
||||||
"corrupted_yaml": "Lectura corrupta de yaml desde {ressource:s} (motivo: {error:s})",
|
"corrupted_yaml": "Lectura corrupta de YAML desde {ressource:s} (motivo: {error:s})",
|
||||||
"info": "Información:",
|
"info": "Información:",
|
||||||
"corrupted_toml": "Lectura corrupta de TOML desde {ressource:s} (motivo: {error:s})",
|
"corrupted_toml": "Lectura corrupta de TOML desde {ressource:s} (motivo: {error:s})",
|
||||||
"warn_the_user_that_lock_is_acquired": "la otra orden recién terminó, iniciando esta orden ahora",
|
"warn_the_user_that_lock_is_acquired": "La otra orden recién terminó, iniciando esta orden ahora",
|
||||||
"warn_the_user_about_waiting_lock_again": "Aún esperando...",
|
"warn_the_user_about_waiting_lock_again": "Aún esperando...",
|
||||||
"warn_the_user_about_waiting_lock": "Otra orden de YunoHost se está ejecutando ahora, estamos esperando a que termine antes de ejecutar esta",
|
"warn_the_user_about_waiting_lock": "Otra orden de YunoHost se está ejecutando ahora, estamos esperando a que termine antes de ejecutar esta",
|
||||||
"invalid_token": "Token invalido - vuelva a autenticarte"
|
"invalid_token": "Token invalido - vuelva a autenticarte",
|
||||||
|
"ldap_server_is_down_restart_it": "El servicio LDAP está caído, intentando reiniciarlo...",
|
||||||
|
"session_expired": "La sesión expiró. Por favor autenticarse de nuevo."
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,7 +18,6 @@
|
||||||
"invalid_password": "Mot de passe incorrect",
|
"invalid_password": "Mot de passe incorrect",
|
||||||
"invalid_usage": "Utilisation erronée, utilisez --help pour accéder à l’aide",
|
"invalid_usage": "Utilisation erronée, utilisez --help pour accéder à l’aide",
|
||||||
"ldap_attribute_already_exists": "L’attribut '{attribute}' existe déjà avec la valeur suivante : '{value}'",
|
"ldap_attribute_already_exists": "L’attribut '{attribute}' existe déjà avec la valeur suivante : '{value}'",
|
||||||
"ldap_operation_error": "Une erreur est survenue lors de l’opération LDAP",
|
|
||||||
"ldap_server_down": "Impossible d’atteindre le serveur LDAP",
|
"ldap_server_down": "Impossible d’atteindre le serveur LDAP",
|
||||||
"logged_in": "Connecté",
|
"logged_in": "Connecté",
|
||||||
"logged_out": "Déconnecté",
|
"logged_out": "Déconnecté",
|
||||||
|
@ -55,6 +54,8 @@
|
||||||
"corrupted_toml": "Fichier TOML corrompu en lecture depuis {ressource:s} (cause : {error:s})",
|
"corrupted_toml": "Fichier TOML corrompu en lecture depuis {ressource:s} (cause : {error:s})",
|
||||||
"warn_the_user_about_waiting_lock": "Une autre commande YunoHost est actuellement en cours, nous attendons qu'elle se termine avant de démarrer celle là",
|
"warn_the_user_about_waiting_lock": "Une autre commande YunoHost est actuellement en cours, nous attendons qu'elle se termine avant de démarrer celle là",
|
||||||
"warn_the_user_about_waiting_lock_again": "Toujours en attente...",
|
"warn_the_user_about_waiting_lock_again": "Toujours en attente...",
|
||||||
"warn_the_user_that_lock_is_acquired": "l'autre commande vient de se terminer, lancement de cette commande",
|
"warn_the_user_that_lock_is_acquired": "La commande précédente vient de se terminer, lancement de cette nouvelle commande",
|
||||||
"invalid_token": "Jeton non valide - veuillez vous authentifier"
|
"invalid_token": "Jeton non valide - veuillez vous authentifier",
|
||||||
|
"ldap_server_is_down_restart_it": "Le service LDAP est arrêté, nous tentons de le redémarrer...",
|
||||||
|
"session_expired": "La session a expiré. Merci de vous ré-authentifier."
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,7 +18,6 @@
|
||||||
"invalid_password": "अवैध पासवर्ड",
|
"invalid_password": "अवैध पासवर्ड",
|
||||||
"invalid_usage": "अवैध उपयोग, सहायता देखने के लिए --help साथ लिखे।",
|
"invalid_usage": "अवैध उपयोग, सहायता देखने के लिए --help साथ लिखे।",
|
||||||
"ldap_attribute_already_exists": "'{attribute}' तर्क पहले इस वैल्यू '{value}' से मौजूद है।",
|
"ldap_attribute_already_exists": "'{attribute}' तर्क पहले इस वैल्यू '{value}' से मौजूद है।",
|
||||||
"ldap_operation_error": "LDAP ऑपरेशन के दौरान त्रुटि हो गई है।",
|
|
||||||
"ldap_server_down": "LDAP सर्वर तक पहुंचने में असमर्थ।",
|
"ldap_server_down": "LDAP सर्वर तक पहुंचने में असमर्थ।",
|
||||||
"logged_in": "लोग्ड इन",
|
"logged_in": "लोग्ड इन",
|
||||||
"logged_out": "लॉग आउट",
|
"logged_out": "लॉग आउट",
|
||||||
|
|
|
@ -20,7 +20,6 @@
|
||||||
"invalid_password": "Password non valida",
|
"invalid_password": "Password non valida",
|
||||||
"invalid_usage": "Utilizzo non valido, usa --help per vedere l'aiuto",
|
"invalid_usage": "Utilizzo non valido, usa --help per vedere l'aiuto",
|
||||||
"ldap_attribute_already_exists": "L'attributo '{attribute}' esiste già con valore '{value}'",
|
"ldap_attribute_already_exists": "L'attributo '{attribute}' esiste già con valore '{value}'",
|
||||||
"ldap_operation_error": "Si è verificato un errore durante l'operazione LDAP",
|
|
||||||
"ldap_server_down": "Impossibile raggiungere il server LDAP",
|
"ldap_server_down": "Impossibile raggiungere il server LDAP",
|
||||||
"logged_in": "Connesso",
|
"logged_in": "Connesso",
|
||||||
"not_logged_in": "Non hai effettuato l'accesso",
|
"not_logged_in": "Non hai effettuato l'accesso",
|
||||||
|
@ -40,21 +39,23 @@
|
||||||
"cannot_open_file": "Impossibile aprire il file {file:s} (motivo: {error:s})",
|
"cannot_open_file": "Impossibile aprire il file {file:s} (motivo: {error:s})",
|
||||||
"cannot_write_file": "Impossibile scrivere il file {file:s} (motivo: {error:s})",
|
"cannot_write_file": "Impossibile scrivere il file {file:s} (motivo: {error:s})",
|
||||||
"unknown_error_reading_file": "Errore sconosciuto durante il tentativo di leggere il file {file:s} (motivo: {errore:s})",
|
"unknown_error_reading_file": "Errore sconosciuto durante il tentativo di leggere il file {file:s} (motivo: {errore:s})",
|
||||||
"corrupted_json": "Lettura json corrotta da {ressource:s} (motivo: {error:s})",
|
"corrupted_json": "Lettura JSON corrotta da {resource:s} (motivo: {error:s})",
|
||||||
"corrupted_yaml": "Lettura yaml corrotta da {ressource:s} (motivo: {error:s})",
|
"corrupted_yaml": "Lettura YAML corrotta da {resource:s} (motivo: {error:s})",
|
||||||
"error_writing_file": "Errore durante la scrittura del file {file:s}: {error:s}",
|
"error_writing_file": "Errore durante la scrittura del file {file:s}: {error:s}",
|
||||||
"error_removing": "Errore durante la rimozione {path:s}: {error:s}",
|
"error_removing": "Errore durante la rimozione {path:s}: {error:s}",
|
||||||
"error_changing_file_permissions": "Errore durante il cambio di permessi per {path:s}: {error:s}",
|
"error_changing_file_permissions": "Errore durante il cambio di permessi per {path:s}: {error:s}",
|
||||||
"invalid_url": "URL non valido {url:s} (questo sito esiste?)",
|
"invalid_url": "URL non valido {url:s} (il sito esiste?)",
|
||||||
"download_ssl_error": "Errore SSL durante la connessione a {url:s}",
|
"download_ssl_error": "Errore SSL durante la connessione a {url:s}",
|
||||||
"download_timeout": "{url:s} ci ha messo troppo a rispondere, abbandonato.",
|
"download_timeout": "{url:s} ci ha messo troppo a rispondere, abbandonato.",
|
||||||
"download_unknown_error": "Errore durante il download di dati da {url:s} : {error:s}",
|
"download_unknown_error": "Errore durante il download di dati da {url:s} : {error:s}",
|
||||||
"download_bad_status_code": "{url:s} ha restituito il codice di stato {code:s}",
|
"download_bad_status_code": "{url:s} ha restituito il codice di stato {code:s}",
|
||||||
"command_unknown": "Comando '{command:s}' sconosciuto ?",
|
"command_unknown": "Comando '{command:s}' sconosciuto?",
|
||||||
"info": "Info:",
|
"info": "Info:",
|
||||||
"warn_the_user_that_lock_is_acquired": "l'altro comando è appena completato, ora avvia questo comando",
|
"warn_the_user_that_lock_is_acquired": "L'altro comando è appena completato, ora avvio questo comando",
|
||||||
"warn_the_user_about_waiting_lock_again": "Sto ancora aspettando ...",
|
"warn_the_user_about_waiting_lock_again": "Sto ancora aspettando ...",
|
||||||
"warn_the_user_about_waiting_lock": "Un altro comando YunoHost è in esecuzione in questo momento, stiamo aspettando che finisca prima di eseguire questo",
|
"warn_the_user_about_waiting_lock": "Un altro comando YunoHost è in esecuzione in questo momento, stiamo aspettando che finisca prima di eseguire questo",
|
||||||
"corrupted_toml": "Toml corrotto da {ressource:s} (motivo: {errore:s})",
|
"corrupted_toml": "TOML corrotto da {ressource:s} (motivo: {errore:s})",
|
||||||
"invalid_token": "Token non valido: autenticare"
|
"invalid_token": "Token non valido: autenticare",
|
||||||
|
"session_expired": "La sessione è terminata. Sei pregato di autenticarti nuovamente.",
|
||||||
|
"ldap_server_is_down_restart_it": "Il servizio LDAP è terminato, provo a riavviarlo..."
|
||||||
}
|
}
|
||||||
|
|
|
@ -1 +1,11 @@
|
||||||
{}
|
{
|
||||||
|
"logged_out": "लग आउट",
|
||||||
|
"password": "पासवर्ड",
|
||||||
|
"deprecated_command_alias": "'{prog} {old}' अस्वीकृत गरिएको छ र भविष्यमा हटाइनेछ, यसको सट्टा '{prog} {new}' प्रयोग गर्नुहोस्।",
|
||||||
|
"deprecated_command": "'{prog} {command}' अस्वीकृत गरिएको छ र भविष्यमा हटाइनेछ",
|
||||||
|
"confirm": "कन्फर्म {prompt}",
|
||||||
|
"colon": "{}: ",
|
||||||
|
"authentication_required_long": "यस कार्य गर्नको लागि प्रमाणीकरण आवाश्यक हुन्छ",
|
||||||
|
"authentication_required": "प्रमाणीकरण आवाश्यक छ",
|
||||||
|
"argument_required": "तर्क '{argument}' आवश्यक छ"
|
||||||
|
}
|
||||||
|
|
|
@ -16,7 +16,6 @@
|
||||||
"invalid_password": "Ongeldig wachtwoord",
|
"invalid_password": "Ongeldig wachtwoord",
|
||||||
"invalid_usage": "Ongeldig gebruik, doe --help om de hulptekst te lezen",
|
"invalid_usage": "Ongeldig gebruik, doe --help om de hulptekst te lezen",
|
||||||
"ldap_attribute_already_exists": "Attribuut '{attribute}' bestaat al met waarde '{value}'",
|
"ldap_attribute_already_exists": "Attribuut '{attribute}' bestaat al met waarde '{value}'",
|
||||||
"ldap_operation_error": "Er is een fout opgetreden bij het uitvoeren van LDAP operatie",
|
|
||||||
"ldap_server_down": "Kan LDAP server niet bereiken",
|
"ldap_server_down": "Kan LDAP server niet bereiken",
|
||||||
"logged_in": "Ingelogd",
|
"logged_in": "Ingelogd",
|
||||||
"logged_out": "Uitgelogd",
|
"logged_out": "Uitgelogd",
|
||||||
|
@ -50,7 +49,7 @@
|
||||||
"download_unknown_error": "Fout tijdens het downloaden van data van {url:s}: {error:s}",
|
"download_unknown_error": "Fout tijdens het downloaden van data van {url:s}: {error:s}",
|
||||||
"download_bad_status_code": "{url:s} stuurt status code {code:s}",
|
"download_bad_status_code": "{url:s} stuurt status code {code:s}",
|
||||||
"command_unknown": "Opdracht '{command:s}' ongekend ?",
|
"command_unknown": "Opdracht '{command:s}' ongekend ?",
|
||||||
"warn_the_user_that_lock_is_acquired": "het andere commando is net voltooid, starten van dit commando",
|
"warn_the_user_that_lock_is_acquired": "de andere opdracht is zojuist voltooid en start nu deze opdracht",
|
||||||
"warn_the_user_about_waiting_lock_again": "Nog steeds aan het wachten...",
|
"warn_the_user_about_waiting_lock_again": "Nog steeds aan het wachten...",
|
||||||
"warn_the_user_about_waiting_lock": "Een ander YunoHost commando wordt uitgevoerd, we wachten tot het gedaan is alovrens dit te starten",
|
"warn_the_user_about_waiting_lock": "Een ander YunoHost commando wordt uitgevoerd, we wachten tot het gedaan is alovrens dit te starten",
|
||||||
"corrupted_toml": "Ongeldige TOML werd gelezen op {ressource:s} (reason: {error:s})",
|
"corrupted_toml": "Ongeldige TOML werd gelezen op {ressource:s} (reason: {error:s})",
|
||||||
|
|
|
@ -31,7 +31,6 @@
|
||||||
"warning": "Atencion :",
|
"warning": "Atencion :",
|
||||||
"invalid_usage": "Usatge invalid, utilizatz --help per accedir a l’ajuda",
|
"invalid_usage": "Usatge invalid, utilizatz --help per accedir a l’ajuda",
|
||||||
"ldap_attribute_already_exists": "L’atribut « {attribute} » existís ja amb la valor : {value}",
|
"ldap_attribute_already_exists": "L’atribut « {attribute} » existís ja amb la valor : {value}",
|
||||||
"ldap_operation_error": "Una error s’es producha pendent l’operacion LDAP",
|
|
||||||
"operation_interrupted": "Operacion interrompuda",
|
"operation_interrupted": "Operacion interrompuda",
|
||||||
"server_already_running": "Un servidor es ja en execucion sus aqueste pòrt",
|
"server_already_running": "Un servidor es ja en execucion sus aqueste pòrt",
|
||||||
"success": "Capitada !",
|
"success": "Capitada !",
|
||||||
|
@ -56,5 +55,7 @@
|
||||||
"warn_the_user_about_waiting_lock": "Una autra comanda YunoHost es en execucion, sèm a esperar qu’acabe abans d’aviar aquesta d’aquí",
|
"warn_the_user_about_waiting_lock": "Una autra comanda YunoHost es en execucion, sèm a esperar qu’acabe abans d’aviar aquesta d’aquí",
|
||||||
"warn_the_user_about_waiting_lock_again": "Encara en espèra…",
|
"warn_the_user_about_waiting_lock_again": "Encara en espèra…",
|
||||||
"warn_the_user_that_lock_is_acquired": "l’autra comanda ven d’acabar, ara lançament d’aquesta comanda",
|
"warn_the_user_that_lock_is_acquired": "l’autra comanda ven d’acabar, ara lançament d’aquesta comanda",
|
||||||
"invalid_token": "Geton invalid - volgatz vos autentificar"
|
"invalid_token": "Geton invalid - volgatz vos autentificar",
|
||||||
|
"ldap_server_is_down_restart_it": "Lo servici LDAP s’es atudat, ensajam de lo reaviar…",
|
||||||
|
"session_expired": "La session a expirat. Tornatz vos autentificar."
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"logged_out": "Wylogowano",
|
"logged_out": "Wylogowano",
|
||||||
"password": "hasło",
|
"password": "Hasło",
|
||||||
"warn_the_user_that_lock_is_acquired": "drugie polecenie właśnie się zakończyło, teraz uruchamia to polecenie",
|
"warn_the_user_that_lock_is_acquired": "drugie polecenie właśnie się zakończyło, teraz uruchamiając to polecenie",
|
||||||
"warn_the_user_about_waiting_lock_again": "Wciąż czekam...",
|
"warn_the_user_about_waiting_lock_again": "Wciąż czekam...",
|
||||||
"warn_the_user_about_waiting_lock": "Kolejne polecenie YunoHost jest teraz uruchomione, czekamy na jego zakończenie przed uruchomieniem tego",
|
"warn_the_user_about_waiting_lock": "Kolejne polecenie YunoHost jest teraz uruchomione, czekamy na jego zakończenie przed uruchomieniem tego",
|
||||||
"command_unknown": "Polecenie „{command:s}” jest nieznane?",
|
"command_unknown": "Polecenie „{command:s}” jest nieznane?",
|
||||||
|
@ -34,7 +34,6 @@
|
||||||
"not_logged_in": "Nie jesteś zalogowany",
|
"not_logged_in": "Nie jesteś zalogowany",
|
||||||
"logged_in": "Zalogowany",
|
"logged_in": "Zalogowany",
|
||||||
"ldap_server_down": "Nie można połączyć się z serwerem LDAP",
|
"ldap_server_down": "Nie można połączyć się z serwerem LDAP",
|
||||||
"ldap_operation_error": "Wystąpił błąd podczas operacji LDAP",
|
|
||||||
"ldap_attribute_already_exists": "Atrybut „{attribute}” już istnieje z wartością „{value}”",
|
"ldap_attribute_already_exists": "Atrybut „{attribute}” już istnieje z wartością „{value}”",
|
||||||
"invalid_usage": "Nieprawidłowe użycie. Przejdź --help, aby wyświetlić pomoc",
|
"invalid_usage": "Nieprawidłowe użycie. Przejdź --help, aby wyświetlić pomoc",
|
||||||
"invalid_token": "Nieprawidłowy token - proszę uwierzytelnić",
|
"invalid_token": "Nieprawidłowy token - proszę uwierzytelnić",
|
||||||
|
|
|
@ -15,7 +15,6 @@
|
||||||
"invalid_password": "Senha incorreta",
|
"invalid_password": "Senha incorreta",
|
||||||
"invalid_usage": "Uso invalido, utilizar --help para ver a ajuda",
|
"invalid_usage": "Uso invalido, utilizar --help para ver a ajuda",
|
||||||
"ldap_attribute_already_exists": "O atributo '{attribute}' já existe com valor '{value}'",
|
"ldap_attribute_already_exists": "O atributo '{attribute}' já existe com valor '{value}'",
|
||||||
"ldap_operation_error": "Um erro ocorreu durante a operação LDAP",
|
|
||||||
"ldap_server_down": "Não foi possível comunicar com o servidor LDAP",
|
"ldap_server_down": "Não foi possível comunicar com o servidor LDAP",
|
||||||
"logged_in": "Sessão iniciada",
|
"logged_in": "Sessão iniciada",
|
||||||
"logged_out": "Sessão terminada",
|
"logged_out": "Sessão terminada",
|
||||||
|
@ -43,18 +42,20 @@
|
||||||
"error_writing_file": "Erro ao gravar arquivo {file:s}: {error:s}",
|
"error_writing_file": "Erro ao gravar arquivo {file:s}: {error:s}",
|
||||||
"error_removing": "Erro ao remover {path:s}: {error:s}",
|
"error_removing": "Erro ao remover {path:s}: {error:s}",
|
||||||
"error_changing_file_permissions": "Erro ao alterar as permissões para {path:s}: {error:s}",
|
"error_changing_file_permissions": "Erro ao alterar as permissões para {path:s}: {error:s}",
|
||||||
"invalid_url": "URL inválida {url:s} (does this site exists ?)",
|
"invalid_url": "URL inválida {url:s} (Esse site existe ?)",
|
||||||
"download_ssl_error": "Erro de SSL ao conectar-se a {url:s}",
|
"download_ssl_error": "Erro de SSL ao conectar-se a {url:s}",
|
||||||
"download_timeout": "{url:s} demorou muito para responder, desistiu.",
|
"download_timeout": "{url:s} demorou muito para responder, desistiu.",
|
||||||
"download_unknown_error": "Erro quando baixando os dados de {url:s} : {error:s}",
|
"download_unknown_error": "Erro quando baixando os dados de {url:s} : {error:s}",
|
||||||
"download_bad_status_code": "{url:s} retornou o código de status {code:s}",
|
"download_bad_status_code": "{url:s} retornou o código de status {code:s}",
|
||||||
"command_unknown": "Comando '{command:s}' desconhecido ?",
|
"command_unknown": "Comando '{command:s}' desconhecido ?",
|
||||||
"corrupted_json": "Json corrompido lido do {ressource:s} (motivo: {error:s})",
|
"corrupted_json": "JSON corrompido lido do {ressource:s} (motivo: {error:s})",
|
||||||
"corrupted_yaml": "Yaml corrompido lido do {ressource:s} (motivo: {error:s})",
|
"corrupted_yaml": "YAML corrompido lido do {ressource:s} (motivo: {error:s})",
|
||||||
"warn_the_user_that_lock_is_acquired": "o outro comando acabou de concluir, agora iniciando este comando",
|
"warn_the_user_that_lock_is_acquired": "O outro comando acabou de concluir, agora iniciando este comando",
|
||||||
"warn_the_user_about_waiting_lock_again": "Ainda esperando...",
|
"warn_the_user_about_waiting_lock_again": "Ainda esperando...",
|
||||||
"warn_the_user_about_waiting_lock": "Outro comando YunoHost está sendo executado agora, estamos aguardando o término antes de executar este",
|
"warn_the_user_about_waiting_lock": "Outro comando YunoHost está sendo executado agora, estamos aguardando o término antes de executar este",
|
||||||
"corrupted_toml": "Toml corrompido lido em {ressource:s} (motivo: {error:s})",
|
"corrupted_toml": "TOML corrompido lido em {ressource:s} (motivo: {error:s})",
|
||||||
"invalid_token": "Token inválido - autentique",
|
"invalid_token": "Token inválido - autentique",
|
||||||
"info": "Informações:"
|
"info": "Informações:",
|
||||||
|
"ldap_server_is_down_restart_it": "O serviço LDAP esta caído, tentando reiniciá-lo...",
|
||||||
|
"session_expired": "A sessão expirou. Se autentique de novo por favor."
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,7 +41,6 @@
|
||||||
"download_timeout": "Превышено время ожидания ответа от {url:s}.",
|
"download_timeout": "Превышено время ожидания ответа от {url:s}.",
|
||||||
"download_unknown_error": "Ошибка при загрузке данных с {url:s} : {error:s}",
|
"download_unknown_error": "Ошибка при загрузке данных с {url:s} : {error:s}",
|
||||||
"instance_already_running": "Операция YunoHost уже запущена. Пожалуйста, подождите, пока он закончится, прежде чем запускать другой.",
|
"instance_already_running": "Операция YunoHost уже запущена. Пожалуйста, подождите, пока он закончится, прежде чем запускать другой.",
|
||||||
"ldap_operation_error": "Ошибка в процессе работы LDAP",
|
|
||||||
"root_required": "Чтобы выполнить это действие, вы должны иметь права root",
|
"root_required": "Чтобы выполнить это действие, вы должны иметь права root",
|
||||||
"corrupted_json": "Повреждённый json получен от {ressource:s} (причина: {error:s})",
|
"corrupted_json": "Повреждённый json получен от {ressource:s} (причина: {error:s})",
|
||||||
"command_unknown": "Команда '{command:s}' неизвестна ?",
|
"command_unknown": "Команда '{command:s}' неизвестна ?",
|
||||||
|
|
|
@ -49,7 +49,6 @@
|
||||||
"unable_retrieve_session": "Det gick inte att hämta sessionen eftersom '{exception}'",
|
"unable_retrieve_session": "Det gick inte att hämta sessionen eftersom '{exception}'",
|
||||||
"unable_authenticate": "Det går inte att verifiera",
|
"unable_authenticate": "Det går inte att verifiera",
|
||||||
"ldap_server_down": "Det går inte att nå LDAP-servern",
|
"ldap_server_down": "Det går inte att nå LDAP-servern",
|
||||||
"ldap_operation_error": "Ett fel inträffade under LDAP-drift",
|
|
||||||
"invalid_usage": "Ogiltig användning, pass --help för att se hjälp",
|
"invalid_usage": "Ogiltig användning, pass --help för att se hjälp",
|
||||||
"invalid_token": "Ogiltigt token - verifiera",
|
"invalid_token": "Ogiltigt token - verifiera",
|
||||||
"instance_already_running": "Det finns redan en YunoHost-operation. Vänta tills den är klar innan du kör en annan.",
|
"instance_already_running": "Det finns redan en YunoHost-operation. Vänta tills den är klar innan du kör en annan.",
|
||||||
|
|
|
@ -11,7 +11,6 @@
|
||||||
"invalid_argument": "Geçersiz argüman '{argument}': {error}",
|
"invalid_argument": "Geçersiz argüman '{argument}': {error}",
|
||||||
"invalid_password": "Geçersiz parola",
|
"invalid_password": "Geçersiz parola",
|
||||||
"ldap_attribute_already_exists": "'{attribute}={value}' özelliği zaten mevcut",
|
"ldap_attribute_already_exists": "'{attribute}={value}' özelliği zaten mevcut",
|
||||||
"ldap_operation_error": "LDAP işlemi sırasında hata oluştu",
|
|
||||||
"ldap_server_down": "LDAP sunucusuna erişilemiyor",
|
"ldap_server_down": "LDAP sunucusuna erişilemiyor",
|
||||||
"logged_in": "Giriş yapıldı",
|
"logged_in": "Giriş yapıldı",
|
||||||
"logged_out": "Çıkış yapıldı",
|
"logged_out": "Çıkış yapıldı",
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
from moulinette.core import (
|
from moulinette.core import (
|
||||||
init_interface,
|
|
||||||
MoulinetteError,
|
MoulinetteError,
|
||||||
MoulinetteSignals,
|
MoulinetteSignals,
|
||||||
Moulinette18n,
|
Moulinette18n,
|
||||||
|
@ -9,8 +8,7 @@ from moulinette.core import (
|
||||||
from moulinette.globals import init_moulinette_env
|
from moulinette.globals import init_moulinette_env
|
||||||
|
|
||||||
__title__ = "moulinette"
|
__title__ = "moulinette"
|
||||||
__version__ = "0.1"
|
__author__ = ["Yunohost Contributors"]
|
||||||
__author__ = ["Kload", "jlebleu", "titoko", "beudbeud", "npze"]
|
|
||||||
__license__ = "AGPL 3.0"
|
__license__ = "AGPL 3.0"
|
||||||
__credits__ = """
|
__credits__ = """
|
||||||
Copyright (C) 2014 YUNOHOST.ORG
|
Copyright (C) 2014 YUNOHOST.ORG
|
||||||
|
@ -73,85 +71,57 @@ def init(logging_config=None, **kwargs):
|
||||||
|
|
||||||
|
|
||||||
# Easy access to interfaces
|
# Easy access to interfaces
|
||||||
|
def api(host="localhost", port=80, routes={}):
|
||||||
|
|
||||||
def api(
|
|
||||||
namespaces, host="localhost", port=80, routes={}, use_websocket=True, use_cache=True
|
|
||||||
):
|
|
||||||
"""Web server (API) interface
|
"""Web server (API) interface
|
||||||
|
|
||||||
Run a HTTP server with the moulinette for an API usage.
|
Run a HTTP server with the moulinette for an API usage.
|
||||||
|
|
||||||
Keyword arguments:
|
Keyword arguments:
|
||||||
- namespaces -- The list of namespaces to use
|
|
||||||
- host -- Server address to bind to
|
- host -- Server address to bind to
|
||||||
- port -- Server port to bind to
|
- port -- Server port to bind to
|
||||||
- routes -- A dict of additional routes to add in the form of
|
- routes -- A dict of additional routes to add in the form of
|
||||||
{(method, uri): callback}
|
{(method, uri): callback}
|
||||||
- use_websocket -- Serve via WSGI to handle asynchronous responses
|
|
||||||
- use_cache -- False if it should parse the actions map file
|
|
||||||
instead of using the cached one
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
from moulinette.interfaces.api import Interface as Api
|
||||||
|
|
||||||
try:
|
try:
|
||||||
moulinette = init_interface(
|
Api(routes=routes).run(host, port)
|
||||||
"api",
|
|
||||||
kwargs={"routes": routes, "use_websocket": use_websocket},
|
|
||||||
actionsmap={"namespaces": namespaces, "use_cache": use_cache},
|
|
||||||
)
|
|
||||||
moulinette.run(host, port)
|
|
||||||
except MoulinetteError as e:
|
except MoulinetteError as e:
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
logging.getLogger(namespaces[0]).error(e.strerror)
|
logging.getLogger(logging.main_logger).error(e.strerror)
|
||||||
return e.errno if hasattr(e, "errno") else 1
|
return 1
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
logging.getLogger(namespaces[0]).info(m18n.g("operation_interrupted"))
|
logging.getLogger(logging.main_logger).info(m18n.g("operation_interrupted"))
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
def cli(
|
def cli(args, top_parser, output_as=None, timeout=None):
|
||||||
namespaces,
|
|
||||||
args,
|
|
||||||
use_cache=True,
|
|
||||||
output_as=None,
|
|
||||||
password=None,
|
|
||||||
timeout=None,
|
|
||||||
parser_kwargs={},
|
|
||||||
):
|
|
||||||
"""Command line interface
|
"""Command line interface
|
||||||
|
|
||||||
Execute an action with the moulinette from the CLI and print its
|
Execute an action with the moulinette from the CLI and print its
|
||||||
result in a readable format.
|
result in a readable format.
|
||||||
|
|
||||||
Keyword arguments:
|
Keyword arguments:
|
||||||
- namespaces -- The list of namespaces to use
|
|
||||||
- args -- A list of argument strings
|
- args -- A list of argument strings
|
||||||
- use_cache -- False if it should parse the actions map file
|
|
||||||
instead of using the cached one
|
|
||||||
- output_as -- Output result in another format, see
|
- output_as -- Output result in another format, see
|
||||||
moulinette.interfaces.cli.Interface for possible values
|
moulinette.interfaces.cli.Interface for possible values
|
||||||
- password -- The password to use in case of authentication
|
- top_parser -- The top parser used to build the ActionsMapParser
|
||||||
- parser_kwargs -- A dict of arguments to pass to the parser
|
|
||||||
class at construction
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
from moulinette.interfaces.cli import Interface as Cli
|
||||||
|
|
||||||
try:
|
try:
|
||||||
moulinette = init_interface(
|
load_only_category = args[0] if args and not args[0].startswith("-") else None
|
||||||
"cli",
|
Cli(top_parser=top_parser, load_only_category=load_only_category).run(
|
||||||
actionsmap={
|
args, output_as=output_as, timeout=timeout
|
||||||
"namespaces": namespaces,
|
|
||||||
"use_cache": use_cache,
|
|
||||||
"parser_kwargs": parser_kwargs,
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
moulinette.run(args, output_as=output_as, password=password, timeout=timeout)
|
|
||||||
except MoulinetteError as e:
|
except MoulinetteError as e:
|
||||||
import logging
|
import logging
|
||||||
|
logging.getLogger(logging.main_logger).error(e.strerror)
|
||||||
logging.getLogger(namespaces[0]).error(e.strerror)
|
|
||||||
return 1
|
return 1
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,28 @@ import os
|
||||||
import re
|
import re
|
||||||
import logging
|
import logging
|
||||||
import yaml
|
import yaml
|
||||||
import pickle as pickle
|
import glob
|
||||||
|
|
||||||
|
import sys
|
||||||
|
if sys.version_info[0] == 3:
|
||||||
|
# python 3
|
||||||
|
import pickle as pickle
|
||||||
|
else:
|
||||||
|
# python 2
|
||||||
|
import cPickle as pickle
|
||||||
|
import codecs
|
||||||
|
import warnings
|
||||||
|
def open(file, mode='r', buffering=-1, encoding=None,
|
||||||
|
errors=None, newline=None, closefd=True, opener=None):
|
||||||
|
if newline is not None:
|
||||||
|
warnings.warn('newline is not supported in py2')
|
||||||
|
if not closefd:
|
||||||
|
warnings.warn('closefd is not supported in py2')
|
||||||
|
if opener is not None:
|
||||||
|
warnings.warn('opener is not supported in py2')
|
||||||
|
return codecs.open(filename=file, mode=mode, encoding=encoding,
|
||||||
|
errors=errors, buffering=buffering)
|
||||||
|
|
||||||
from time import time
|
from time import time
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
from importlib import import_module
|
from importlib import import_module
|
||||||
|
@ -282,7 +303,6 @@ class ExtraArgumentParser(object):
|
||||||
if iface in klass.skipped_iface:
|
if iface in klass.skipped_iface:
|
||||||
continue
|
continue
|
||||||
self.extra[klass.name] = klass
|
self.extra[klass.name] = klass
|
||||||
logger.debug("extra parameter classes loaded: %s", self.extra.keys())
|
|
||||||
|
|
||||||
def validate(self, arg_name, parameters):
|
def validate(self, arg_name, parameters):
|
||||||
"""
|
"""
|
||||||
|
@ -294,7 +314,7 @@ class ExtraArgumentParser(object):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
# Iterate over parameters to validate
|
# Iterate over parameters to validate
|
||||||
for p, v in parameters.items():
|
for p in list(parameters):
|
||||||
klass = self.extra.get(p, None)
|
klass = self.extra.get(p, None)
|
||||||
if not klass:
|
if not klass:
|
||||||
# Remove unknown parameters
|
# Remove unknown parameters
|
||||||
|
@ -302,7 +322,7 @@ class ExtraArgumentParser(object):
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
# Validate parameter value
|
# Validate parameter value
|
||||||
parameters[p] = klass.validate(v, arg_name)
|
parameters[p] = klass.validate(parameters[p], arg_name)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(
|
logger.error(
|
||||||
"unable to validate extra parameter '%s' "
|
"unable to validate extra parameter '%s' "
|
||||||
|
@ -398,36 +418,32 @@ class ActionsMap(object):
|
||||||
Moreover, the action can have specific argument(s).
|
Moreover, the action can have specific argument(s).
|
||||||
|
|
||||||
This class allows to manipulate one or several actions maps
|
This class allows to manipulate one or several actions maps
|
||||||
associated to a namespace. If no namespace is given, it will load
|
associated to a namespace.
|
||||||
all available namespaces.
|
|
||||||
|
|
||||||
Keyword arguments:
|
Keyword arguments:
|
||||||
- parser_class -- The BaseActionsMapParser derived class to use
|
- top_parser -- A BaseActionsMapParser-derived instance to use for
|
||||||
for parsing the actions map
|
parsing the actions map
|
||||||
- namespaces -- The list of namespaces to use
|
- load_only_category -- A name of a category that should only be the
|
||||||
- use_cache -- False if it should parse the actions map file
|
one loaded because it's been already determined
|
||||||
instead of using the cached one
|
that's the only one relevant ... used for optimization
|
||||||
- parser_kwargs -- A dict of arguments to pass to the parser
|
purposes...
|
||||||
class at construction
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, parser_class, namespaces=[], use_cache=True, parser_kwargs={}):
|
def __init__(self, top_parser, load_only_category=None):
|
||||||
if not issubclass(parser_class, BaseActionsMapParser):
|
|
||||||
raise ValueError("Invalid parser class '%s'" % parser_class.__name__)
|
assert isinstance(top_parser, BaseActionsMapParser), (
|
||||||
self.parser_class = parser_class
|
"Invalid parser class '%s'" % top_parser.__class__.__name__
|
||||||
self.use_cache = use_cache
|
)
|
||||||
|
|
||||||
moulinette_env = init_moulinette_env()
|
moulinette_env = init_moulinette_env()
|
||||||
DATA_DIR = moulinette_env["DATA_DIR"]
|
DATA_DIR = moulinette_env["DATA_DIR"]
|
||||||
CACHE_DIR = moulinette_env["CACHE_DIR"]
|
CACHE_DIR = moulinette_env["CACHE_DIR"]
|
||||||
|
|
||||||
if len(namespaces) == 0:
|
|
||||||
namespaces = self.get_namespaces()
|
|
||||||
actionsmaps = OrderedDict()
|
actionsmaps = OrderedDict()
|
||||||
|
|
||||||
|
self.from_cache = False
|
||||||
# Iterate over actions map namespaces
|
# Iterate over actions map namespaces
|
||||||
for n in namespaces:
|
for n in self.get_namespaces():
|
||||||
logger.debug("loading actions map namespace '%s'", n)
|
logger.debug("loading actions map namespace '%s'", n)
|
||||||
|
|
||||||
actionsmap_yml = "%s/actionsmap/%s.yml" % (DATA_DIR, n)
|
actionsmap_yml = "%s/actionsmap/%s.yml" % (DATA_DIR, n)
|
||||||
|
@ -439,33 +455,36 @@ class ActionsMap(object):
|
||||||
actionsmap_yml_stat.st_mtime,
|
actionsmap_yml_stat.st_mtime,
|
||||||
)
|
)
|
||||||
|
|
||||||
if use_cache and os.path.exists(actionsmap_pkl):
|
if os.path.exists(actionsmap_pkl):
|
||||||
try:
|
try:
|
||||||
# Attempt to load cache
|
# Attempt to load cache
|
||||||
with open(actionsmap_pkl, "rb") as f:
|
with open(actionsmap_pkl, "rb") as f:
|
||||||
actionsmaps[n] = pickle.load(f)
|
actionsmaps[n] = pickle.load(f)
|
||||||
|
|
||||||
|
self.from_cache = True
|
||||||
# TODO: Switch to python3 and catch proper exception
|
# TODO: Switch to python3 and catch proper exception
|
||||||
except (IOError, EOFError):
|
except (IOError, EOFError):
|
||||||
self.use_cache = False
|
actionsmaps[n] = self.generate_cache(n)
|
||||||
actionsmaps = self.generate_cache(namespaces)
|
else: # cache file doesn't exists
|
||||||
elif use_cache: # cached file doesn't exists
|
actionsmaps[n] = self.generate_cache(n)
|
||||||
self.use_cache = False
|
|
||||||
actionsmaps = self.generate_cache(namespaces)
|
# If load_only_category is set, and *if* the target category
|
||||||
elif n not in actionsmaps:
|
# is in the actionsmap, we'll load only that one.
|
||||||
with open(actionsmap_yml) as f:
|
# If we filter it even if it doesn't exist, we'll end up with a
|
||||||
actionsmaps[n] = ordered_yaml_load(f)
|
# weird help message when we do a typo in the category name..
|
||||||
|
if load_only_category and load_only_category in actionsmaps[n]:
|
||||||
|
actionsmaps[n] = {
|
||||||
|
k: v
|
||||||
|
for k, v in actionsmaps[n].items()
|
||||||
|
if k in [load_only_category, "_global"]
|
||||||
|
}
|
||||||
|
|
||||||
# Load translations
|
# Load translations
|
||||||
m18n.load_namespace(n)
|
m18n.load_namespace(n)
|
||||||
|
|
||||||
# Generate parsers
|
# Generate parsers
|
||||||
self.extraparser = ExtraArgumentParser(parser_class.interface)
|
self.extraparser = ExtraArgumentParser(top_parser.interface)
|
||||||
self._parser = self._construct_parser(actionsmaps, **parser_kwargs)
|
self.parser = self._construct_parser(actionsmaps, top_parser)
|
||||||
|
|
||||||
@property
|
|
||||||
def parser(self):
|
|
||||||
"""Return the instance of the interface's actions map parser"""
|
|
||||||
return self._parser
|
|
||||||
|
|
||||||
def get_authenticator_for_profile(self, auth_profile):
|
def get_authenticator_for_profile(self, auth_profile):
|
||||||
|
|
||||||
|
@ -563,6 +582,9 @@ class ActionsMap(object):
|
||||||
)
|
)
|
||||||
func = getattr(mod, func_name)
|
func = getattr(mod, func_name)
|
||||||
except (AttributeError, ImportError):
|
except (AttributeError, ImportError):
|
||||||
|
import traceback
|
||||||
|
|
||||||
|
traceback.print_exc()
|
||||||
logger.exception("unable to load function %s.%s", namespace, func_name)
|
logger.exception("unable to load function %s.%s", namespace, func_name)
|
||||||
raise MoulinetteError("error_see_log")
|
raise MoulinetteError("error_see_log")
|
||||||
else:
|
else:
|
||||||
|
@ -601,85 +623,81 @@ class ActionsMap(object):
|
||||||
moulinette_env = init_moulinette_env()
|
moulinette_env = init_moulinette_env()
|
||||||
DATA_DIR = moulinette_env["DATA_DIR"]
|
DATA_DIR = moulinette_env["DATA_DIR"]
|
||||||
|
|
||||||
for f in os.listdir("%s/actionsmap" % DATA_DIR):
|
# This var is ['*'] by default but could be set for example to
|
||||||
if f.endswith(".yml"):
|
# ['yunohost', 'yml_*']
|
||||||
namespaces.append(f[:-4])
|
NAMESPACE_PATTERNS = moulinette_env["NAMESPACES"]
|
||||||
|
|
||||||
|
# Look for all files that match the given patterns in the actionsmap dir
|
||||||
|
for namespace_pattern in NAMESPACE_PATTERNS:
|
||||||
|
namespaces.extend(
|
||||||
|
glob.glob("%s/actionsmap/%s.yml" % (DATA_DIR, namespace_pattern))
|
||||||
|
)
|
||||||
|
|
||||||
|
# Keep only the filenames with extension
|
||||||
|
namespaces = [os.path.basename(n)[:-4] for n in namespaces]
|
||||||
|
|
||||||
return namespaces
|
return namespaces
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def generate_cache(klass, namespaces=None):
|
def generate_cache(klass, namespace):
|
||||||
"""
|
"""
|
||||||
Generate cache for the actions map's file(s)
|
Generate cache for the actions map's file(s)
|
||||||
|
|
||||||
Keyword arguments:
|
Keyword arguments:
|
||||||
- namespaces -- A list of namespaces to generate cache for
|
- namespace -- The namespace to generate cache for
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
A dict of actions map for each namespaces
|
The action map for the namespace
|
||||||
|
|
||||||
"""
|
"""
|
||||||
moulinette_env = init_moulinette_env()
|
moulinette_env = init_moulinette_env()
|
||||||
CACHE_DIR = moulinette_env["CACHE_DIR"]
|
CACHE_DIR = moulinette_env["CACHE_DIR"]
|
||||||
DATA_DIR = moulinette_env["DATA_DIR"]
|
DATA_DIR = moulinette_env["DATA_DIR"]
|
||||||
|
|
||||||
actionsmaps = {}
|
|
||||||
if not namespaces:
|
|
||||||
namespaces = klass.get_namespaces()
|
|
||||||
|
|
||||||
# Iterate over actions map namespaces
|
# Iterate over actions map namespaces
|
||||||
for n in namespaces:
|
logger.debug("generating cache for actions map namespace '%s'", namespace)
|
||||||
logger.debug("generating cache for actions map namespace '%s'", n)
|
|
||||||
|
|
||||||
# Read actions map from yaml file
|
# Read actions map from yaml file
|
||||||
am_file = "%s/actionsmap/%s.yml" % (DATA_DIR, n)
|
am_file = "%s/actionsmap/%s.yml" % (DATA_DIR, namespace)
|
||||||
with open(am_file, "r") as f:
|
with open(am_file, "r") as f:
|
||||||
actionsmaps[n] = ordered_yaml_load(f)
|
actionsmap = ordered_yaml_load(f)
|
||||||
|
|
||||||
# at installation, cachedir might not exists
|
# at installation, cachedir might not exists
|
||||||
if os.path.exists("%s/actionsmap/" % CACHE_DIR):
|
for old_cache in glob.glob("%s/actionsmap/%s-*.pkl" % (CACHE_DIR, namespace)):
|
||||||
# clean old cached files
|
os.remove(old_cache)
|
||||||
for i in os.listdir("%s/actionsmap/" % CACHE_DIR):
|
|
||||||
if i.endswith(".pkl"):
|
|
||||||
os.remove("%s/actionsmap/%s" % (CACHE_DIR, i))
|
|
||||||
|
|
||||||
# Cache actions map into pickle file
|
# Cache actions map into pickle file
|
||||||
am_file_stat = os.stat(am_file)
|
am_file_stat = os.stat(am_file)
|
||||||
|
|
||||||
pkl = "%s-%d-%d.pkl" % (n, am_file_stat.st_size, am_file_stat.st_mtime)
|
pkl = "%s-%d-%d.pkl" % (namespace, am_file_stat.st_size, am_file_stat.st_mtime)
|
||||||
|
|
||||||
with open_cachefile(pkl, "wb", subdir="actionsmap") as f:
|
with open_cachefile(pkl, "wb", subdir="actionsmap") as f:
|
||||||
pickle.dump(actionsmaps[n], f)
|
pickle.dump(actionsmap, f)
|
||||||
|
|
||||||
return actionsmaps
|
return actionsmap
|
||||||
|
|
||||||
# Private methods
|
# Private methods
|
||||||
|
|
||||||
def _construct_parser(self, actionsmaps, **kwargs):
|
def _construct_parser(self, actionsmaps, top_parser):
|
||||||
"""
|
"""
|
||||||
Construct the parser with the actions map
|
Construct the parser with the actions map
|
||||||
|
|
||||||
Keyword arguments:
|
Keyword arguments:
|
||||||
- actionsmaps -- A dict of multi-level dictionnary of
|
- actionsmaps -- A dict of multi-level dictionnary of
|
||||||
categories/actions/arguments list for each namespaces
|
categories/actions/arguments list for each namespaces
|
||||||
- **kwargs -- Additionnal arguments to pass at the parser
|
- top_parser -- A BaseActionsMapParser-derived instance to use for
|
||||||
class instantiation
|
parsing the actions map
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
An interface relevant's parser object
|
An interface relevant's parser object
|
||||||
|
|
||||||
"""
|
"""
|
||||||
# Get extra parameters
|
|
||||||
if self.use_cache:
|
|
||||||
validate_extra = False
|
|
||||||
else:
|
|
||||||
validate_extra = True
|
|
||||||
|
|
||||||
# Instantiate parser
|
logger.debug("building parser...")
|
||||||
#
|
start = time()
|
||||||
# this either returns:
|
|
||||||
# * moulinette.interfaces.cli.ActionsMapParser
|
# If loading from cache, extra were already checked when cache was
|
||||||
# * moulinette.interfaces.api.ActionsMapParser
|
# loaded ? Not sure about this ... old code is a bit mysterious...
|
||||||
top_parser = self.parser_class(**kwargs)
|
validate_extra = not self.from_cache
|
||||||
|
|
||||||
# namespace, actionsmap is a tuple where:
|
# namespace, actionsmap is a tuple where:
|
||||||
#
|
#
|
||||||
|
@ -781,4 +799,5 @@ class ActionsMap(object):
|
||||||
tid, action_options["configuration"]
|
tid, action_options["configuration"]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
logger.debug("building parser took %.3fs", time() - start)
|
||||||
return top_parser
|
return top_parser
|
||||||
|
|
|
@ -5,7 +5,7 @@ import logging
|
||||||
import hashlib
|
import hashlib
|
||||||
import hmac
|
import hmac
|
||||||
|
|
||||||
from moulinette.cache import open_cachefile, get_cachedir
|
from moulinette.cache import open_cachefile, get_cachedir, cachefile_exists
|
||||||
from moulinette.core import MoulinetteError
|
from moulinette.core import MoulinetteError
|
||||||
|
|
||||||
logger = logging.getLogger("moulinette.authenticator")
|
logger = logging.getLogger("moulinette.authenticator")
|
||||||
|
@ -159,6 +159,10 @@ class BaseAuthenticator(object):
|
||||||
"%s.asc" % session_id, mode, subdir="session/%s" % self.name
|
"%s.asc" % session_id, mode, subdir="session/%s" % self.name
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def _session_exists(self, session_id):
|
||||||
|
"""Check a session exists"""
|
||||||
|
return cachefile_exists("%s.asc" % session_id, subdir="session/%s" % self.name)
|
||||||
|
|
||||||
def _store_session(self, session_id, session_token):
|
def _store_session(self, session_id, session_token):
|
||||||
"""Store a session to be able to use it later to reauthenticate"""
|
"""Store a session to be able to use it later to reauthenticate"""
|
||||||
|
|
||||||
|
@ -170,6 +174,8 @@ class BaseAuthenticator(object):
|
||||||
|
|
||||||
def _authenticate_session(self, session_id, session_token):
|
def _authenticate_session(self, session_id, session_token):
|
||||||
"""Checks session and token against the stored session token"""
|
"""Checks session and token against the stored session token"""
|
||||||
|
if not self._session_exists(session_id):
|
||||||
|
raise MoulinetteError("session_expired")
|
||||||
try:
|
try:
|
||||||
# FIXME : shouldn't we also add a check that this session file
|
# FIXME : shouldn't we also add a check that this session file
|
||||||
# is not too old ? e.g. not older than 24 hours ? idk...
|
# is not too old ? e.g. not older than 24 hours ? idk...
|
||||||
|
|
|
@ -2,15 +2,15 @@
|
||||||
|
|
||||||
# TODO: Use Python3 to remove this fix!
|
# TODO: Use Python3 to remove this fix!
|
||||||
from __future__ import absolute_import
|
from __future__ import absolute_import
|
||||||
|
import os
|
||||||
import logging
|
import logging
|
||||||
import random
|
|
||||||
import string
|
|
||||||
import crypt
|
|
||||||
import ldap
|
import ldap
|
||||||
import ldap.sasl
|
import ldap.sasl
|
||||||
|
import time
|
||||||
import ldap.modlist as modlist
|
import ldap.modlist as modlist
|
||||||
|
|
||||||
from moulinette.core import MoulinetteError
|
from moulinette import m18n
|
||||||
|
from moulinette.core import MoulinetteError, MoulinetteLdapIsDownError
|
||||||
from moulinette.authenticators import BaseAuthenticator
|
from moulinette.authenticators import BaseAuthenticator
|
||||||
|
|
||||||
logger = logging.getLogger("moulinette.authenticator.ldap")
|
logger = logging.getLogger("moulinette.authenticator.ldap")
|
||||||
|
@ -69,7 +69,7 @@ class Authenticator(BaseAuthenticator):
|
||||||
# Implement virtual methods
|
# Implement virtual methods
|
||||||
|
|
||||||
def authenticate(self, password=None):
|
def authenticate(self, password=None):
|
||||||
try:
|
def _reconnect():
|
||||||
con = ldap.ldapobject.ReconnectLDAPObject(
|
con = ldap.ldapobject.ReconnectLDAPObject(
|
||||||
self._get_uri(), retry_max=10, retry_delay=0.5
|
self._get_uri(), retry_max=10, retry_delay=0.5
|
||||||
)
|
)
|
||||||
|
@ -80,11 +80,23 @@ class Authenticator(BaseAuthenticator):
|
||||||
con.simple_bind_s(self.userdn, password)
|
con.simple_bind_s(self.userdn, password)
|
||||||
else:
|
else:
|
||||||
con.simple_bind_s()
|
con.simple_bind_s()
|
||||||
|
|
||||||
|
return con
|
||||||
|
|
||||||
|
try:
|
||||||
|
con = _reconnect()
|
||||||
except ldap.INVALID_CREDENTIALS:
|
except ldap.INVALID_CREDENTIALS:
|
||||||
raise MoulinetteError("invalid_password")
|
raise MoulinetteError("invalid_password")
|
||||||
except ldap.SERVER_DOWN:
|
except ldap.SERVER_DOWN:
|
||||||
logger.exception("unable to reach the server to authenticate")
|
# ldap is down, attempt to restart it before really failing
|
||||||
raise MoulinetteError("ldap_server_down")
|
logger.warning(m18n.g("ldap_server_is_down_restart_it"))
|
||||||
|
os.system("systemctl restart slapd")
|
||||||
|
time.sleep(10) # waits 10 secondes so we are sure that slapd has restarted
|
||||||
|
|
||||||
|
try:
|
||||||
|
con = _reconnect()
|
||||||
|
except ldap.SERVER_DOWN:
|
||||||
|
raise MoulinetteLdapIsDownError("ldap_server_down")
|
||||||
|
|
||||||
# Check that we are indeed logged in with the right identity
|
# Check that we are indeed logged in with the right identity
|
||||||
try:
|
try:
|
||||||
|
@ -99,30 +111,6 @@ class Authenticator(BaseAuthenticator):
|
||||||
raise MoulinetteError("Not logged in with the expected userdn ?!")
|
raise MoulinetteError("Not logged in with the expected userdn ?!")
|
||||||
else:
|
else:
|
||||||
self.con = con
|
self.con = con
|
||||||
self._ensure_password_uses_strong_hash(password)
|
|
||||||
|
|
||||||
def _ensure_password_uses_strong_hash(self, password):
|
|
||||||
# XXX this has been copy pasted from YunoHost, should we put that into moulinette?
|
|
||||||
def _hash_user_password(password):
|
|
||||||
char_set = (
|
|
||||||
string.ascii_uppercase + string.ascii_lowercase + string.digits + "./"
|
|
||||||
)
|
|
||||||
salt = "".join([random.SystemRandom().choice(char_set) for x in range(16)])
|
|
||||||
salt = "$6$" + salt + "$"
|
|
||||||
return "{CRYPT}" + crypt.crypt(str(password), salt)
|
|
||||||
|
|
||||||
hashed_password = self.search(self.admindn, attrs=["userPassword"])[0]
|
|
||||||
|
|
||||||
# post-install situation, password is not already set
|
|
||||||
if "userPassword" not in hashed_password or not hashed_password["userPassword"]:
|
|
||||||
return
|
|
||||||
|
|
||||||
# we aren't using sha-512 but something else that is weaker, proceed to upgrade
|
|
||||||
if not hashed_password["userPassword"][0].startswith("{CRYPT}$6$"):
|
|
||||||
self.update(
|
|
||||||
"cn=%s" % self.adminuser,
|
|
||||||
{"userPassword": [_hash_user_password(password)]},
|
|
||||||
)
|
|
||||||
|
|
||||||
# Additional LDAP methods
|
# Additional LDAP methods
|
||||||
# TODO: Review these methods
|
# TODO: Review these methods
|
||||||
|
@ -148,15 +136,11 @@ class Authenticator(BaseAuthenticator):
|
||||||
try:
|
try:
|
||||||
result = self.con.search_s(base, ldap.SCOPE_SUBTREE, filter, attrs)
|
result = self.con.search_s(base, ldap.SCOPE_SUBTREE, filter, attrs)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.exception(
|
raise MoulinetteError(
|
||||||
"error during LDAP search operation with: base='%s', "
|
"error during LDAP search operation with: base='%s', "
|
||||||
"filter='%s', attrs=%s and exception %s",
|
"filter='%s', attrs=%s and exception %s" % (base, filter, attrs, e),
|
||||||
base,
|
raw_msg=True,
|
||||||
filter,
|
|
||||||
attrs,
|
|
||||||
e,
|
|
||||||
)
|
)
|
||||||
raise MoulinetteError("ldap_operation_error", action="search")
|
|
||||||
|
|
||||||
result_list = []
|
result_list = []
|
||||||
if not attrs or "dn" not in attrs:
|
if not attrs or "dn" not in attrs:
|
||||||
|
@ -185,14 +169,11 @@ class Authenticator(BaseAuthenticator):
|
||||||
try:
|
try:
|
||||||
self.con.add_s(dn, ldif)
|
self.con.add_s(dn, ldif)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.exception(
|
raise MoulinetteError(
|
||||||
"error during LDAP add operation with: rdn='%s', "
|
"error during LDAP add operation with: rdn='%s', "
|
||||||
"attr_dict=%s and exception %s",
|
"attr_dict=%s and exception %s" % (rdn, attr_dict, e),
|
||||||
rdn,
|
raw_msg=True,
|
||||||
attr_dict,
|
|
||||||
e,
|
|
||||||
)
|
)
|
||||||
raise MoulinetteError("ldap_operation_error", action="add")
|
|
||||||
else:
|
else:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
@ -211,12 +192,11 @@ class Authenticator(BaseAuthenticator):
|
||||||
try:
|
try:
|
||||||
self.con.delete_s(dn)
|
self.con.delete_s(dn)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.exception(
|
raise MoulinetteError(
|
||||||
"error during LDAP delete operation with: rdn='%s' and exception %s",
|
"error during LDAP delete operation with: rdn='%s' and exception %s"
|
||||||
rdn,
|
% (rdn, e),
|
||||||
e,
|
raw_msg=True,
|
||||||
)
|
)
|
||||||
raise MoulinetteError("ldap_operation_error", action="remove")
|
|
||||||
else:
|
else:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
@ -249,15 +229,12 @@ class Authenticator(BaseAuthenticator):
|
||||||
|
|
||||||
self.con.modify_ext_s(dn, ldif)
|
self.con.modify_ext_s(dn, ldif)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.exception(
|
raise MoulinetteError(
|
||||||
"error during LDAP update operation with: rdn='%s', "
|
"error during LDAP update operation with: rdn='%s', "
|
||||||
"attr_dict=%s, new_rdn=%s and exception: %s",
|
"attr_dict=%s, new_rdn=%s and exception: %s"
|
||||||
rdn,
|
% (rdn, attr_dict, new_rdn, e),
|
||||||
attr_dict,
|
raw_msg=True,
|
||||||
new_rdn,
|
|
||||||
e,
|
|
||||||
)
|
)
|
||||||
raise MoulinetteError("ldap_operation_error", action="update")
|
|
||||||
else:
|
else:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
|
@ -42,3 +42,10 @@ def open_cachefile(filename, mode="r", subdir=""):
|
||||||
cache_dir = get_cachedir(subdir, make_dir=True if mode[0] == "w" else False)
|
cache_dir = get_cachedir(subdir, make_dir=True if mode[0] == "w" else False)
|
||||||
file_path = os.path.join(cache_dir, filename)
|
file_path = os.path.join(cache_dir, filename)
|
||||||
return open(file_path, mode)
|
return open(file_path, mode)
|
||||||
|
|
||||||
|
|
||||||
|
def cachefile_exists(filename, subdir=""):
|
||||||
|
|
||||||
|
cache_dir = get_cachedir(subdir, make_dir=False)
|
||||||
|
file_path = os.path.join(cache_dir, filename)
|
||||||
|
return os.path.exists(file_path)
|
||||||
|
|
|
@ -5,8 +5,6 @@ import time
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from importlib import import_module
|
|
||||||
|
|
||||||
import moulinette
|
import moulinette
|
||||||
from moulinette.globals import init_moulinette_env
|
from moulinette.globals import init_moulinette_env
|
||||||
|
|
||||||
|
@ -372,53 +370,6 @@ class MoulinetteSignals(object):
|
||||||
raise NotImplementedError("this signal is not handled")
|
raise NotImplementedError("this signal is not handled")
|
||||||
|
|
||||||
|
|
||||||
# Interfaces & Authenticators management -------------------------------
|
|
||||||
|
|
||||||
|
|
||||||
def init_interface(name, kwargs={}, actionsmap={}):
|
|
||||||
"""Return a new interface instance
|
|
||||||
|
|
||||||
Retrieve the given interface module and return a new instance of its
|
|
||||||
Interface class. It is initialized with arguments 'kwargs' and
|
|
||||||
connected to 'actionsmap' if it's an ActionsMap object, otherwise
|
|
||||||
a new ActionsMap instance will be initialized with arguments
|
|
||||||
'actionsmap'.
|
|
||||||
|
|
||||||
Keyword arguments:
|
|
||||||
- name -- The interface name
|
|
||||||
- kwargs -- A dict of arguments to pass to Interface
|
|
||||||
- actionsmap -- Either an ActionsMap instance or a dict of
|
|
||||||
arguments to pass to ActionsMap
|
|
||||||
|
|
||||||
"""
|
|
||||||
from moulinette.actionsmap import ActionsMap
|
|
||||||
|
|
||||||
try:
|
|
||||||
mod = import_module("moulinette.interfaces.%s" % name)
|
|
||||||
except ImportError as e:
|
|
||||||
logger.exception("unable to load interface '%s' : %s", name, e)
|
|
||||||
raise MoulinetteError("error_see_log")
|
|
||||||
else:
|
|
||||||
try:
|
|
||||||
# Retrieve interface classes
|
|
||||||
parser = mod.ActionsMapParser
|
|
||||||
interface = mod.Interface
|
|
||||||
except AttributeError:
|
|
||||||
logger.exception("unable to retrieve classes of interface '%s'", name)
|
|
||||||
raise MoulinetteError("error_see_log")
|
|
||||||
|
|
||||||
# Instantiate or retrieve ActionsMap
|
|
||||||
if isinstance(actionsmap, dict):
|
|
||||||
amap = ActionsMap(actionsmap.pop("parser", parser), **actionsmap)
|
|
||||||
elif isinstance(actionsmap, ActionsMap):
|
|
||||||
amap = actionsmap
|
|
||||||
else:
|
|
||||||
logger.error("invalid actionsmap value %r", actionsmap)
|
|
||||||
raise MoulinetteError("error_see_log")
|
|
||||||
|
|
||||||
return interface(amap, **kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
# Moulinette core classes ----------------------------------------------
|
# Moulinette core classes ----------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
@ -435,6 +386,10 @@ class MoulinetteError(Exception):
|
||||||
self.strerror = msg
|
self.strerror = msg
|
||||||
|
|
||||||
|
|
||||||
|
class MoulinetteLdapIsDownError(MoulinetteError):
|
||||||
|
"""Used when ldap is down"""
|
||||||
|
|
||||||
|
|
||||||
class MoulinetteLock(object):
|
class MoulinetteLock(object):
|
||||||
|
|
||||||
"""Locker for a moulinette instance
|
"""Locker for a moulinette instance
|
||||||
|
|
|
@ -11,4 +11,7 @@ def init_moulinette_env():
|
||||||
"MOULINETTE_LOCALES_DIR", "/usr/share/moulinette/locale"
|
"MOULINETTE_LOCALES_DIR", "/usr/share/moulinette/locale"
|
||||||
),
|
),
|
||||||
"CACHE_DIR": environ.get("MOULINETTE_CACHE_DIR", "/var/cache/moulinette"),
|
"CACHE_DIR": environ.get("MOULINETTE_CACHE_DIR", "/var/cache/moulinette"),
|
||||||
|
"NAMESPACES": environ.get(
|
||||||
|
"MOULINETTE_NAMESPACES", "*"
|
||||||
|
).split(), # By default we'll load every namespace we find
|
||||||
}
|
}
|
||||||
|
|
|
@ -342,11 +342,6 @@ class _CallbackAction(argparse.Action):
|
||||||
self.callback_method = callback.get("method")
|
self.callback_method = callback.get("method")
|
||||||
self.callback_kwargs = callback.get("kwargs", {})
|
self.callback_kwargs = callback.get("kwargs", {})
|
||||||
self.callback_return = callback.get("return", False)
|
self.callback_return = callback.get("return", False)
|
||||||
logger.debug(
|
|
||||||
"registering new callback action '{0}' to {1}".format(
|
|
||||||
self.callback_method, option_strings
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def callback(self):
|
def callback(self):
|
||||||
|
@ -361,6 +356,9 @@ class _CallbackAction(argparse.Action):
|
||||||
mod = __import__(mod_name, globals=globals(), level=0, fromlist=[func_name])
|
mod = __import__(mod_name, globals=globals(), level=0, fromlist=[func_name])
|
||||||
func = getattr(mod, func_name)
|
func = getattr(mod, func_name)
|
||||||
except (AttributeError, ImportError):
|
except (AttributeError, ImportError):
|
||||||
|
import traceback
|
||||||
|
|
||||||
|
traceback.print_exc()
|
||||||
raise ValueError("unable to import method {0}".format(self.callback_method))
|
raise ValueError("unable to import method {0}".format(self.callback_method))
|
||||||
self._callback = func
|
self._callback = func
|
||||||
|
|
||||||
|
|
|
@ -10,10 +10,11 @@ from gevent import sleep
|
||||||
from gevent.queue import Queue
|
from gevent.queue import Queue
|
||||||
from geventwebsocket import WebSocketError
|
from geventwebsocket import WebSocketError
|
||||||
|
|
||||||
from bottle import run, request, response, Bottle, HTTPResponse
|
from bottle import request, response, Bottle, HTTPResponse
|
||||||
from bottle import abort
|
from bottle import abort
|
||||||
|
|
||||||
from moulinette import msignals, m18n, env
|
from moulinette import msignals, m18n, env
|
||||||
|
from moulinette.actionsmap import ActionsMap
|
||||||
from moulinette.core import MoulinetteError
|
from moulinette.core import MoulinetteError
|
||||||
from moulinette.interfaces import (
|
from moulinette.interfaces import (
|
||||||
BaseActionsMapParser,
|
BaseActionsMapParser,
|
||||||
|
@ -219,22 +220,18 @@ class _ActionsMapPlugin(object):
|
||||||
|
|
||||||
Keyword arguments:
|
Keyword arguments:
|
||||||
- actionsmap -- An ActionsMap instance
|
- actionsmap -- An ActionsMap instance
|
||||||
- use_websocket -- If true, install a WebSocket on /messages in order
|
|
||||||
to serve messages coming from the 'display' signal
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
name = "actionsmap"
|
name = "actionsmap"
|
||||||
api = 2
|
api = 2
|
||||||
|
|
||||||
def __init__(self, actionsmap, use_websocket, log_queues={}):
|
def __init__(self, actionsmap, log_queues={}):
|
||||||
# Connect signals to handlers
|
# Connect signals to handlers
|
||||||
msignals.set_handler("authenticate", self._do_authenticate)
|
msignals.set_handler("authenticate", self._do_authenticate)
|
||||||
if use_websocket:
|
msignals.set_handler("display", self._do_display)
|
||||||
msignals.set_handler("display", self._do_display)
|
|
||||||
|
|
||||||
self.actionsmap = actionsmap
|
self.actionsmap = actionsmap
|
||||||
self.use_websocket = use_websocket
|
|
||||||
self.log_queues = log_queues
|
self.log_queues = log_queues
|
||||||
# TODO: Save and load secrets?
|
# TODO: Save and load secrets?
|
||||||
self.secrets = {}
|
self.secrets = {}
|
||||||
|
@ -290,13 +287,9 @@ class _ActionsMapPlugin(object):
|
||||||
)
|
)
|
||||||
|
|
||||||
# Append messages route
|
# Append messages route
|
||||||
if self.use_websocket:
|
app.route(
|
||||||
app.route(
|
"/messages", name="messages", callback=self.messages, skip=["actionsmap"],
|
||||||
"/messages",
|
)
|
||||||
name="messages",
|
|
||||||
callback=self.messages,
|
|
||||||
skip=["actionsmap"],
|
|
||||||
)
|
|
||||||
|
|
||||||
# Append routes from the actions map
|
# Append routes from the actions map
|
||||||
for (m, p) in self.actionsmap.parser.routes:
|
for (m, p) in self.actionsmap.parser.routes:
|
||||||
|
@ -327,11 +320,10 @@ class _ActionsMapPlugin(object):
|
||||||
# Append other request params
|
# Append other request params
|
||||||
for k, v in request.params.dict.items():
|
for k, v in request.params.dict.items():
|
||||||
v = _format(v)
|
v = _format(v)
|
||||||
try:
|
if k not in params.keys():
|
||||||
curr_v = params[k]
|
|
||||||
except KeyError:
|
|
||||||
params[k] = v
|
params[k] = v
|
||||||
else:
|
else:
|
||||||
|
curr_v = params[k]
|
||||||
# Append param value to the list
|
# Append param value to the list
|
||||||
if not isinstance(curr_v, list):
|
if not isinstance(curr_v, list):
|
||||||
curr_v = [curr_v]
|
curr_v = [curr_v]
|
||||||
|
@ -362,13 +354,24 @@ class _ActionsMapPlugin(object):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
# Retrieve session values
|
# Retrieve session values
|
||||||
s_id = request.get_cookie("session.id") or random_ascii()
|
try:
|
||||||
|
s_id = request.get_cookie("session.id") or random_ascii()
|
||||||
|
except:
|
||||||
|
# Super rare case where there are super weird cookie / cache issue
|
||||||
|
# Previous line throws a CookieError that creates a 500 error ...
|
||||||
|
# So let's catch it and just use a fresh ID then...
|
||||||
|
s_id = random_ascii()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
s_secret = self.secrets[s_id]
|
s_secret = self.secrets[s_id]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
s_tokens = {}
|
s_tokens = {}
|
||||||
else:
|
else:
|
||||||
s_tokens = request.get_cookie("session.tokens", secret=s_secret) or {}
|
try:
|
||||||
|
s_tokens = request.get_cookie("session.tokens", secret=s_secret) or {}
|
||||||
|
except:
|
||||||
|
# Same as for session.id a few lines before
|
||||||
|
s_tokens = {}
|
||||||
s_new_token = random_ascii()
|
s_new_token = random_ascii()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
@ -574,6 +577,9 @@ def format_for_response(content):
|
||||||
return ""
|
return ""
|
||||||
response.status = 200
|
response.status = 200
|
||||||
|
|
||||||
|
if isinstance(content, HTTPResponse):
|
||||||
|
return content
|
||||||
|
|
||||||
# Return JSON-style response
|
# Return JSON-style response
|
||||||
response.content_type = "application/json"
|
response.content_type = "application/json"
|
||||||
return json_encode(content, cls=JSONExtendedEncoder)
|
return json_encode(content, cls=JSONExtendedEncoder)
|
||||||
|
@ -735,17 +741,16 @@ class Interface(BaseInterface):
|
||||||
actions map.
|
actions map.
|
||||||
|
|
||||||
Keyword arguments:
|
Keyword arguments:
|
||||||
- actionsmap -- The ActionsMap instance to connect to
|
|
||||||
- routes -- A dict of additional routes to add in the form of
|
- routes -- A dict of additional routes to add in the form of
|
||||||
{(method, path): callback}
|
{(method, path): callback}
|
||||||
- use_websocket -- Serve via WSGI to handle asynchronous responses
|
|
||||||
- log_queues -- A LogQueues object or None to retrieve it from
|
- log_queues -- A LogQueues object or None to retrieve it from
|
||||||
registered logging handlers
|
registered logging handlers
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, actionsmap, routes={}, use_websocket=True, log_queues=None):
|
def __init__(self, routes={}, log_queues=None):
|
||||||
self.use_websocket = use_websocket
|
|
||||||
|
actionsmap = ActionsMap(ActionsMapParser())
|
||||||
|
|
||||||
# Attempt to retrieve log queues from an APIQueueHandler
|
# Attempt to retrieve log queues from an APIQueueHandler
|
||||||
if log_queues is None:
|
if log_queues is None:
|
||||||
|
@ -766,18 +771,21 @@ class Interface(BaseInterface):
|
||||||
|
|
||||||
# Attempt to retrieve and set locale
|
# Attempt to retrieve and set locale
|
||||||
def api18n(callback):
|
def api18n(callback):
|
||||||
try:
|
def wrapper(*args, **kwargs):
|
||||||
locale = request.params.pop("locale")
|
try:
|
||||||
except KeyError:
|
locale = request.params.pop("locale")
|
||||||
locale = m18n.default_locale
|
except KeyError:
|
||||||
m18n.set_locale(locale)
|
locale = m18n.default_locale
|
||||||
return callback
|
m18n.set_locale(locale)
|
||||||
|
return callback(*args, **kwargs)
|
||||||
|
|
||||||
|
return wrapper
|
||||||
|
|
||||||
# Install plugins
|
# Install plugins
|
||||||
app.install(filter_csrf)
|
app.install(filter_csrf)
|
||||||
app.install(apiheader)
|
app.install(apiheader)
|
||||||
app.install(api18n)
|
app.install(api18n)
|
||||||
app.install(_ActionsMapPlugin(actionsmap, use_websocket, log_queues))
|
app.install(_ActionsMapPlugin(actionsmap, log_queues))
|
||||||
|
|
||||||
# Append default routes
|
# Append default routes
|
||||||
# app.route(['/api', '/api/<category:re:[a-z]+>'], method='GET',
|
# app.route(['/api', '/api/<category:re:[a-z]+>'], method='GET',
|
||||||
|
@ -802,23 +810,15 @@ class Interface(BaseInterface):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
logger.debug(
|
logger.debug(
|
||||||
"starting the server instance in %s:%d with websocket=%s",
|
"starting the server instance in %s:%d", host, port,
|
||||||
host,
|
|
||||||
port,
|
|
||||||
self.use_websocket,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if self.use_websocket:
|
from gevent.pywsgi import WSGIServer
|
||||||
from gevent.pywsgi import WSGIServer
|
from geventwebsocket.handler import WebSocketHandler
|
||||||
from geventwebsocket.handler import WebSocketHandler
|
|
||||||
|
|
||||||
server = WSGIServer(
|
server = WSGIServer((host, port), self._app, handler_class=WebSocketHandler)
|
||||||
(host, port), self._app, handler_class=WebSocketHandler
|
server.serve_forever()
|
||||||
)
|
|
||||||
server.serve_forever()
|
|
||||||
else:
|
|
||||||
run(self._app, host=host, port=port)
|
|
||||||
except IOError as e:
|
except IOError as e:
|
||||||
logger.exception("unable to start the server instance on %s:%d", host, port)
|
logger.exception("unable to start the server instance on %s:%d", host, port)
|
||||||
if e.args[0] == errno.EADDRINUSE:
|
if e.args[0] == errno.EADDRINUSE:
|
||||||
|
|
|
@ -7,12 +7,12 @@ import locale
|
||||||
import logging
|
import logging
|
||||||
from argparse import SUPPRESS
|
from argparse import SUPPRESS
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
import pytz
|
|
||||||
from datetime import date, datetime
|
from datetime import date, datetime
|
||||||
|
|
||||||
import argcomplete
|
import argcomplete
|
||||||
|
|
||||||
from moulinette import msignals, m18n
|
from moulinette import msignals, m18n
|
||||||
|
from moulinette.actionsmap import ActionsMap
|
||||||
from moulinette.core import MoulinetteError
|
from moulinette.core import MoulinetteError
|
||||||
from moulinette.interfaces import (
|
from moulinette.interfaces import (
|
||||||
BaseActionsMapParser,
|
BaseActionsMapParser,
|
||||||
|
@ -103,6 +103,8 @@ def pretty_date(_date):
|
||||||
Argument:
|
Argument:
|
||||||
- date -- The date or datetime to display
|
- date -- The date or datetime to display
|
||||||
"""
|
"""
|
||||||
|
import pytz # Lazy loading, this takes like 3+ sec on a RPi2 ?!
|
||||||
|
|
||||||
# Deduce system timezone
|
# Deduce system timezone
|
||||||
nowutc = datetime.now(tz=pytz.utc)
|
nowutc = datetime.now(tz=pytz.utc)
|
||||||
nowtz = datetime.now()
|
nowtz = datetime.now()
|
||||||
|
@ -168,8 +170,13 @@ def pretty_print_dict(d, depth=0):
|
||||||
|
|
||||||
|
|
||||||
def get_locale():
|
def get_locale():
|
||||||
"""Return current user locale"""
|
"""Return current user eocale"""
|
||||||
lang = locale.getdefaultlocale()[0]
|
try:
|
||||||
|
lang = locale.getdefaultlocale()[0]
|
||||||
|
except Exception:
|
||||||
|
# In some edge case the locale lib fails ...
|
||||||
|
# c.f. https://forum.yunohost.org/t/error-when-trying-to-enter-user-information-in-admin-panel/11390/11
|
||||||
|
lang = os.getenv("LANG")
|
||||||
if not lang:
|
if not lang:
|
||||||
return ""
|
return ""
|
||||||
return lang[:2]
|
return lang[:2]
|
||||||
|
@ -418,7 +425,8 @@ class Interface(BaseInterface):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, actionsmap):
|
def __init__(self, top_parser=None, load_only_category=None):
|
||||||
|
|
||||||
# Set user locale
|
# Set user locale
|
||||||
m18n.set_locale(get_locale())
|
m18n.set_locale(get_locale())
|
||||||
|
|
||||||
|
@ -428,9 +436,12 @@ class Interface(BaseInterface):
|
||||||
msignals.set_handler("authenticate", self._do_authenticate)
|
msignals.set_handler("authenticate", self._do_authenticate)
|
||||||
msignals.set_handler("prompt", self._do_prompt)
|
msignals.set_handler("prompt", self._do_prompt)
|
||||||
|
|
||||||
self.actionsmap = actionsmap
|
self.actionsmap = ActionsMap(
|
||||||
|
ActionsMapParser(top_parser=top_parser),
|
||||||
|
load_only_category=load_only_category,
|
||||||
|
)
|
||||||
|
|
||||||
def run(self, args, output_as=None, password=None, timeout=None):
|
def run(self, args, output_as=None, timeout=None):
|
||||||
"""Run the moulinette
|
"""Run the moulinette
|
||||||
|
|
||||||
Process the action corresponding to the given arguments 'args'
|
Process the action corresponding to the given arguments 'args'
|
||||||
|
@ -442,7 +453,6 @@ class Interface(BaseInterface):
|
||||||
- json: return a JSON encoded string
|
- json: return a JSON encoded string
|
||||||
- plain: return a script-readable output
|
- plain: return a script-readable output
|
||||||
- none: do not output the result
|
- none: do not output the result
|
||||||
- password -- The password to use in case of authentication
|
|
||||||
- timeout -- Number of seconds before this command will timeout because it can't acquire the lock (meaning that another command is currently running), by default there is no timeout and the command will wait until it can get the lock
|
- timeout -- Number of seconds before this command will timeout because it can't acquire the lock (meaning that another command is currently running), by default there is no timeout and the command will wait until it can get the lock
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
@ -453,11 +463,7 @@ class Interface(BaseInterface):
|
||||||
argcomplete.autocomplete(self.actionsmap.parser._parser)
|
argcomplete.autocomplete(self.actionsmap.parser._parser)
|
||||||
|
|
||||||
# Set handler for authentication
|
# Set handler for authentication
|
||||||
if password:
|
msignals.set_handler("authenticate", self._do_authenticate)
|
||||||
msignals.set_handler("authenticate", lambda a: a(password=password))
|
|
||||||
else:
|
|
||||||
if os.isatty(1):
|
|
||||||
msignals.set_handler("authenticate", self._do_authenticate)
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
ret = self.actionsmap.process(args, timeout=timeout)
|
ret = self.actionsmap.process(args, timeout=timeout)
|
||||||
|
@ -489,7 +495,11 @@ class Interface(BaseInterface):
|
||||||
Handle the core.MoulinetteSignals.authenticate signal.
|
Handle the core.MoulinetteSignals.authenticate signal.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
# TODO: Allow token authentication?
|
# Hmpf we have no-use case in yunohost anymore where we need to auth
|
||||||
|
# because everything is run as root ...
|
||||||
|
# I guess we could imagine some yunohost-independant use-case where
|
||||||
|
# moulinette is used to create a CLI for non-root user that needs to
|
||||||
|
# auth somehow but hmpf -.-
|
||||||
help = authenticator.extra.get("help")
|
help = authenticator.extra.get("help")
|
||||||
msg = m18n.n(help) if help else m18n.g("password")
|
msg = m18n.n(help) if help else m18n.g("password")
|
||||||
return authenticator(password=self._do_prompt(msg, True, False, color="yellow"))
|
return authenticator(password=self._do_prompt(msg, True, False, color="yellow"))
|
||||||
|
|
|
@ -65,6 +65,7 @@ def configure_logging(logging_config=None):
|
||||||
# load configuration from dict
|
# load configuration from dict
|
||||||
dictConfig(DEFAULT_LOGGING)
|
dictConfig(DEFAULT_LOGGING)
|
||||||
if logging_config:
|
if logging_config:
|
||||||
|
logging.main_logger = logging_config.get("main_logger")
|
||||||
dictConfig(logging_config)
|
dictConfig(logging_config)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -28,7 +28,7 @@ def check_output(args, stderr=subprocess.STDOUT, shell=True, **kwargs):
|
||||||
and use shell by default before calling subprocess.check_output.
|
and use shell by default before calling subprocess.check_output.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
return subprocess.check_output(args, stderr=stderr, shell=shell, **kwargs)
|
return subprocess.check_output(args, stderr=stderr, shell=shell, **kwargs).strip()
|
||||||
|
|
||||||
|
|
||||||
# Call with stream access ----------------------------------------------
|
# Call with stream access ----------------------------------------------
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
import logging
|
import logging
|
||||||
from json.encoder import JSONEncoder
|
from json.encoder import JSONEncoder
|
||||||
import datetime
|
import datetime
|
||||||
import pytz
|
|
||||||
|
|
||||||
logger = logging.getLogger("moulinette.utils.serialize")
|
logger = logging.getLogger("moulinette.utils.serialize")
|
||||||
|
|
||||||
|
@ -23,6 +22,9 @@ class JSONExtendedEncoder(JSONEncoder):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def default(self, o):
|
def default(self, o):
|
||||||
|
|
||||||
|
import pytz # Lazy loading, this takes like 3+ sec on a RPi2 ?!
|
||||||
|
|
||||||
"""Return a serializable object"""
|
"""Return a serializable object"""
|
||||||
# Convert compatible containers into list
|
# Convert compatible containers into list
|
||||||
if isinstance(o, set) or (hasattr(o, "__iter__") and hasattr(o, "next")):
|
if isinstance(o, set) or (hasattr(o, "__iter__") and hasattr(o, "next")):
|
||||||
|
|
49
setup.py
49
setup.py
|
@ -17,6 +17,31 @@ if "install" in sys.argv:
|
||||||
if f.endswith('.json'):
|
if f.endswith('.json'):
|
||||||
locale_files.append('locales/%s' % f)
|
locale_files.append('locales/%s' % f)
|
||||||
|
|
||||||
|
install_deps = [
|
||||||
|
'argcomplete',
|
||||||
|
'psutil',
|
||||||
|
'pytz',
|
||||||
|
'pyyaml',
|
||||||
|
'toml',
|
||||||
|
'python-ldap',
|
||||||
|
'gevent-websocket',
|
||||||
|
'bottle',
|
||||||
|
]
|
||||||
|
|
||||||
|
test_deps = [
|
||||||
|
'pytest',
|
||||||
|
'pytest-cov',
|
||||||
|
'pytest-env',
|
||||||
|
'pytest-mock',
|
||||||
|
'requests',
|
||||||
|
'requests-mock',
|
||||||
|
'webtest'
|
||||||
|
]
|
||||||
|
extras = {
|
||||||
|
'install': install_deps,
|
||||||
|
'tests': test_deps,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
setup(name='Moulinette',
|
setup(name='Moulinette',
|
||||||
version='2.0.0',
|
version='2.0.0',
|
||||||
|
@ -27,24 +52,8 @@ setup(name='Moulinette',
|
||||||
license='AGPL',
|
license='AGPL',
|
||||||
packages=find_packages(exclude=['test']),
|
packages=find_packages(exclude=['test']),
|
||||||
data_files=[(LOCALES_DIR, locale_files)],
|
data_files=[(LOCALES_DIR, locale_files)],
|
||||||
python_requires='>=3.5',
|
python_requires='>=2.7.*, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4',
|
||||||
install_requires=[
|
install_requires=install_deps,
|
||||||
'argcomplete',
|
tests_require=test_deps,
|
||||||
'psutil',
|
extras_require=extras,
|
||||||
'pytz',
|
|
||||||
'pyyaml',
|
|
||||||
'toml',
|
|
||||||
'python-ldap',
|
|
||||||
'gevent-websocket',
|
|
||||||
'bottle',
|
|
||||||
],
|
|
||||||
tests_require=[
|
|
||||||
'pytest',
|
|
||||||
'pytest-cov',
|
|
||||||
'pytest-env',
|
|
||||||
'pytest-mock',
|
|
||||||
'requests',
|
|
||||||
'requests-mock',
|
|
||||||
'webtest'
|
|
||||||
],
|
|
||||||
)
|
)
|
||||||
|
|
|
@ -7,7 +7,7 @@ import os
|
||||||
import shutil
|
import shutil
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from src.ldap_server import LDAPServer
|
from .src.ldap_server import LDAPServer
|
||||||
|
|
||||||
|
|
||||||
def patch_init(moulinette):
|
def patch_init(moulinette):
|
||||||
|
@ -125,13 +125,9 @@ def moulinette_webapi(moulinette):
|
||||||
|
|
||||||
CookiePolicy.return_ok_secure = return_true
|
CookiePolicy.return_ok_secure = return_true
|
||||||
|
|
||||||
moulinette_webapi = moulinette.core.init_interface(
|
from moulinette.interfaces.api import Interface as Api
|
||||||
"api",
|
|
||||||
kwargs={"routes": {}, "use_websocket": False},
|
|
||||||
actionsmap={"namespaces": ["moulitest"], "use_cache": True},
|
|
||||||
)
|
|
||||||
|
|
||||||
return TestApp(moulinette_webapi._app)
|
return TestApp(Api(routes={})._app)
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
|
@ -148,17 +144,12 @@ def moulinette_cli(moulinette, mocker):
|
||||||
help="Log and print debug messages",
|
help="Log and print debug messages",
|
||||||
)
|
)
|
||||||
mocker.patch("os.isatty", return_value=True)
|
mocker.patch("os.isatty", return_value=True)
|
||||||
moulinette_cli = moulinette.core.init_interface(
|
from moulinette.interfaces.cli import Interface as Cli
|
||||||
"cli",
|
|
||||||
actionsmap={
|
cli = Cli(top_parser=parser)
|
||||||
"namespaces": ["moulitest"],
|
|
||||||
"use_cache": False,
|
|
||||||
"parser_kwargs": {"top_parser": parser},
|
|
||||||
},
|
|
||||||
)
|
|
||||||
mocker.stopall()
|
mocker.stopall()
|
||||||
|
|
||||||
return moulinette_cli
|
return cli
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
|
|
|
@ -98,7 +98,23 @@ class LDAPServer:
|
||||||
"posixAccount",
|
"posixAccount",
|
||||||
"simpleSecurityObject",
|
"simpleSecurityObject",
|
||||||
],
|
],
|
||||||
"userPassword": ["yunohost"],
|
"userPassword": [self._hash_user_password("yunohost")],
|
||||||
}
|
}
|
||||||
|
|
||||||
ldap_interface.update("cn=admin", admin_dict)
|
ldap_interface.update("cn=admin", admin_dict)
|
||||||
|
|
||||||
|
def _hash_user_password(self, password):
|
||||||
|
"""
|
||||||
|
Copy pasta of what's in yunohost/user.py
|
||||||
|
"""
|
||||||
|
import string
|
||||||
|
import random
|
||||||
|
import crypt
|
||||||
|
|
||||||
|
char_set = (
|
||||||
|
string.ascii_uppercase + string.ascii_lowercase + string.digits + "./"
|
||||||
|
)
|
||||||
|
salt = "".join([random.SystemRandom().choice(char_set) for x in range(16)])
|
||||||
|
|
||||||
|
salt = "$6$" + salt + "$"
|
||||||
|
return "{CRYPT}" + crypt.crypt(str(password), salt)
|
||||||
|
|
|
@ -158,10 +158,10 @@ def test_required_paremeter_missing_value(iface, caplog):
|
||||||
|
|
||||||
def test_actions_map_unknown_authenticator(monkeypatch, tmp_path):
|
def test_actions_map_unknown_authenticator(monkeypatch, tmp_path):
|
||||||
monkeypatch.setenv("MOULINETTE_DATA_DIR", str(tmp_path))
|
monkeypatch.setenv("MOULINETTE_DATA_DIR", str(tmp_path))
|
||||||
actionsmap_dir = actionsmap_dir = tmp_path / "actionsmap"
|
actionsmap_dir = tmp_path / "actionsmap"
|
||||||
actionsmap_dir.mkdir()
|
actionsmap_dir.mkdir()
|
||||||
|
|
||||||
amap = ActionsMap(BaseActionsMapParser)
|
amap = ActionsMap(BaseActionsMapParser())
|
||||||
with pytest.raises(ValueError) as exception:
|
with pytest.raises(ValueError) as exception:
|
||||||
amap.get_authenticator_for_profile("unknown")
|
amap.get_authenticator_for_profile("unknown")
|
||||||
assert "Unknown authenticator" in str(exception)
|
assert "Unknown authenticator" in str(exception)
|
||||||
|
@ -225,7 +225,7 @@ def test_extra_argument_parser_parse_args(iface, mocker):
|
||||||
def test_actions_map_api():
|
def test_actions_map_api():
|
||||||
from moulinette.interfaces.api import ActionsMapParser
|
from moulinette.interfaces.api import ActionsMapParser
|
||||||
|
|
||||||
amap = ActionsMap(ActionsMapParser, use_cache=False)
|
amap = ActionsMap(ActionsMapParser())
|
||||||
|
|
||||||
assert amap.parser.global_conf["authenticate"] == "all"
|
assert amap.parser.global_conf["authenticate"] == "all"
|
||||||
assert "default" in amap.parser.global_conf["authenticator"]
|
assert "default" in amap.parser.global_conf["authenticator"]
|
||||||
|
@ -233,9 +233,9 @@ def test_actions_map_api():
|
||||||
assert ("GET", "/test-auth/default") in amap.parser.routes
|
assert ("GET", "/test-auth/default") in amap.parser.routes
|
||||||
assert ("POST", "/test-auth/subcat/post") in amap.parser.routes
|
assert ("POST", "/test-auth/subcat/post") in amap.parser.routes
|
||||||
|
|
||||||
amap.generate_cache()
|
amap.generate_cache("moulitest")
|
||||||
|
|
||||||
amap = ActionsMap(ActionsMapParser, use_cache=True)
|
amap = ActionsMap(ActionsMapParser())
|
||||||
|
|
||||||
assert amap.parser.global_conf["authenticate"] == "all"
|
assert amap.parser.global_conf["authenticate"] == "all"
|
||||||
assert "default" in amap.parser.global_conf["authenticator"]
|
assert "default" in amap.parser.global_conf["authenticator"]
|
||||||
|
@ -247,17 +247,24 @@ def test_actions_map_api():
|
||||||
def test_actions_map_import_error(mocker):
|
def test_actions_map_import_error(mocker):
|
||||||
from moulinette.interfaces.api import ActionsMapParser
|
from moulinette.interfaces.api import ActionsMapParser
|
||||||
|
|
||||||
amap = ActionsMap(ActionsMapParser)
|
amap = ActionsMap(ActionsMapParser())
|
||||||
|
|
||||||
from moulinette.core import MoulinetteLock
|
from moulinette.core import MoulinetteLock
|
||||||
|
|
||||||
mocker.patch.object(MoulinetteLock, "_is_son_of", return_value=False)
|
mocker.patch.object(MoulinetteLock, "_is_son_of", return_value=False)
|
||||||
|
|
||||||
mocker.patch("__builtin__.__import__", side_effect=ImportError)
|
orig_import = __import__
|
||||||
|
|
||||||
|
def import_mock(name, globals={}, locals={}, fromlist=[], level=-1):
|
||||||
|
if name == "moulitest.testauth":
|
||||||
|
mocker.stopall()
|
||||||
|
raise ImportError
|
||||||
|
return orig_import(name, globals, locals, fromlist, level)
|
||||||
|
|
||||||
|
mocker.patch("__builtin__.__import__", side_effect=import_mock)
|
||||||
with pytest.raises(MoulinetteError) as exception:
|
with pytest.raises(MoulinetteError) as exception:
|
||||||
amap.process({}, timeout=30, route=("GET", "/test-auth/none"))
|
amap.process({}, timeout=30, route=("GET", "/test-auth/none"))
|
||||||
|
|
||||||
mocker.stopall()
|
|
||||||
translation = m18n.g("error_see_log")
|
translation = m18n.g("error_see_log")
|
||||||
expected_msg = translation.format()
|
expected_msg = translation.format()
|
||||||
assert expected_msg in str(exception)
|
assert expected_msg in str(exception)
|
||||||
|
@ -274,9 +281,7 @@ def test_actions_map_cli():
|
||||||
default=False,
|
default=False,
|
||||||
help="Log and print debug messages",
|
help="Log and print debug messages",
|
||||||
)
|
)
|
||||||
amap = ActionsMap(
|
amap = ActionsMap(ActionsMapParser(top_parser=parser))
|
||||||
ActionsMapParser, use_cache=False, parser_kwargs={"top_parser": parser}
|
|
||||||
)
|
|
||||||
|
|
||||||
assert amap.parser.global_conf["authenticate"] == "all"
|
assert amap.parser.global_conf["authenticate"] == "all"
|
||||||
assert "default" in amap.parser.global_conf["authenticator"]
|
assert "default" in amap.parser.global_conf["authenticator"]
|
||||||
|
@ -293,11 +298,9 @@ def test_actions_map_cli():
|
||||||
.choices
|
.choices
|
||||||
)
|
)
|
||||||
|
|
||||||
amap.generate_cache()
|
amap.generate_cache("moulitest")
|
||||||
|
|
||||||
amap = ActionsMap(
|
amap = ActionsMap(ActionsMapParser(top_parser=parser))
|
||||||
ActionsMapParser, use_cache=True, parser_kwargs={"top_parser": parser}
|
|
||||||
)
|
|
||||||
|
|
||||||
assert amap.parser.global_conf["authenticate"] == "all"
|
assert amap.parser.global_conf["authenticate"] == "all"
|
||||||
assert "default" in amap.parser.global_conf["authenticator"]
|
assert "default" in amap.parser.global_conf["authenticator"]
|
||||||
|
|
|
@ -216,18 +216,15 @@ class TestAuthCLI:
|
||||||
|
|
||||||
assert "some_data_from_default" in message.out
|
assert "some_data_from_default" in message.out
|
||||||
|
|
||||||
moulinette_cli.run(
|
moulinette_cli.run(["testauth", "default"], output_as="plain")
|
||||||
["testauth", "default"], output_as="plain", password="default"
|
|
||||||
)
|
|
||||||
message = capsys.readouterr()
|
message = capsys.readouterr()
|
||||||
|
|
||||||
assert "some_data_from_default" in message.out
|
assert "some_data_from_default" in message.out
|
||||||
|
|
||||||
def test_login_bad_password(self, moulinette_cli, capsys, mocker):
|
def test_login_bad_password(self, moulinette_cli, capsys, mocker):
|
||||||
|
mocker.patch("getpass.getpass", return_value="Bad Password")
|
||||||
with pytest.raises(MoulinetteError):
|
with pytest.raises(MoulinetteError):
|
||||||
moulinette_cli.run(
|
moulinette_cli.run(["testauth", "default"], output_as="plain")
|
||||||
["testauth", "default"], output_as="plain", password="Bad Password"
|
|
||||||
)
|
|
||||||
|
|
||||||
mocker.patch("getpass.getpass", return_value="Bad Password")
|
mocker.patch("getpass.getpass", return_value="Bad Password")
|
||||||
with pytest.raises(MoulinetteError):
|
with pytest.raises(MoulinetteError):
|
||||||
|
@ -242,10 +239,9 @@ class TestAuthCLI:
|
||||||
expected_msg = translation.format()
|
expected_msg = translation.format()
|
||||||
assert expected_msg in str(exception)
|
assert expected_msg in str(exception)
|
||||||
|
|
||||||
|
mocker.patch("getpass.getpass", return_value="yoloswag")
|
||||||
with pytest.raises(MoulinetteError) as exception:
|
with pytest.raises(MoulinetteError) as exception:
|
||||||
moulinette_cli.run(
|
moulinette_cli.run(["testauth", "default"], output_as="none")
|
||||||
["testauth", "default"], output_as="none", password="yoloswag"
|
|
||||||
)
|
|
||||||
|
|
||||||
expected_msg = translation.format()
|
expected_msg = translation.format()
|
||||||
assert expected_msg in str(exception)
|
assert expected_msg in str(exception)
|
||||||
|
|
|
@ -371,6 +371,11 @@ def test_mkdir(tmp_path):
|
||||||
|
|
||||||
|
|
||||||
def test_mkdir_with_permission(tmp_path, mocker):
|
def test_mkdir_with_permission(tmp_path, mocker):
|
||||||
|
|
||||||
|
# This test only make sense when not being root
|
||||||
|
if os.getuid() == 0:
|
||||||
|
return
|
||||||
|
|
||||||
new_path = tmp_path / "new_folder"
|
new_path = tmp_path / "new_folder"
|
||||||
permission = 0o700
|
permission = 0o700
|
||||||
mkdir(str(new_path), mode=permission)
|
mkdir(str(new_path), mode=permission)
|
||||||
|
|
|
@ -66,11 +66,14 @@ class TestLDAP:
|
||||||
|
|
||||||
assert ldap_interface.con
|
assert ldap_interface.con
|
||||||
|
|
||||||
def test_authenticate_server_down(self, ldap_server):
|
def test_authenticate_server_down(self, ldap_server, mocker):
|
||||||
self.ldap_conf["parameters"]["uri"] = ldap_server.uri
|
self.ldap_conf["parameters"]["uri"] = ldap_server.uri
|
||||||
self.ldap_conf["parameters"]["user_rdn"] = "cn=admin,dc=yunohost,dc=org"
|
self.ldap_conf["parameters"]["user_rdn"] = "cn=admin,dc=yunohost,dc=org"
|
||||||
ldap_server.stop()
|
ldap_server.stop()
|
||||||
ldap_interface = m_ldap.Authenticator(**self.ldap_conf)
|
ldap_interface = m_ldap.Authenticator(**self.ldap_conf)
|
||||||
|
|
||||||
|
# Now if slapd is down, moulinette tries to restart it
|
||||||
|
mocker.patch("os.system")
|
||||||
with pytest.raises(MoulinetteError) as exception:
|
with pytest.raises(MoulinetteError) as exception:
|
||||||
ldap_interface.authenticate(password="yunohost")
|
ldap_interface.authenticate(password="yunohost")
|
||||||
|
|
||||||
|
@ -208,9 +211,10 @@ class TestLDAP:
|
||||||
with pytest.raises(MoulinetteError) as exception:
|
with pytest.raises(MoulinetteError) as exception:
|
||||||
self.add_new_user(ldap_interface)
|
self.add_new_user(ldap_interface)
|
||||||
|
|
||||||
translation = m18n.g("ldap_operation_error", action="add")
|
expected_message = "error during LDAP add operation with: rdn="
|
||||||
expected_msg = translation.format(action="add")
|
expected_error = "modifications require authentication"
|
||||||
assert expected_msg in str(exception)
|
assert expected_error in str(exception)
|
||||||
|
assert expected_message in str(exception)
|
||||||
|
|
||||||
def remove_new_user(self, ldap_interface):
|
def remove_new_user(self, ldap_interface):
|
||||||
new_user_info = self.add_new_user(
|
new_user_info = self.add_new_user(
|
||||||
|
@ -229,9 +233,10 @@ class TestLDAP:
|
||||||
"uid=%s,ou=users,dc=yunohost,dc=org" % uid, attrs=None
|
"uid=%s,ou=users,dc=yunohost,dc=org" % uid, attrs=None
|
||||||
)
|
)
|
||||||
|
|
||||||
translation = m18n.g("ldap_operation_error", action="search")
|
expected_message = "error during LDAP search operation with: base="
|
||||||
expected_msg = translation.format(action="search")
|
expected_error = "No such object"
|
||||||
assert expected_msg in str(exception)
|
assert expected_error in str(exception)
|
||||||
|
assert expected_message in str(exception)
|
||||||
|
|
||||||
def test_admin_remove(self, ldap_server):
|
def test_admin_remove(self, ldap_server):
|
||||||
self.ldap_conf["parameters"]["uri"] = ldap_server.uri
|
self.ldap_conf["parameters"]["uri"] = ldap_server.uri
|
||||||
|
@ -257,9 +262,10 @@ class TestLDAP:
|
||||||
with pytest.raises(MoulinetteError) as exception:
|
with pytest.raises(MoulinetteError) as exception:
|
||||||
self.remove_new_user(ldap_interface)
|
self.remove_new_user(ldap_interface)
|
||||||
|
|
||||||
translation = m18n.g("ldap_operation_error", action="remove")
|
expected_message = "error during LDAP delete operation with: rdn="
|
||||||
expected_msg = translation.format(action="remove")
|
expected_error = "modifications require authentication"
|
||||||
assert expected_msg in str(exception)
|
assert expected_error in str(exception)
|
||||||
|
assert expected_message in str(exception)
|
||||||
|
|
||||||
def update_new_user(self, ldap_interface, new_rdn=False):
|
def update_new_user(self, ldap_interface, new_rdn=False):
|
||||||
new_user_info = self.add_new_user(
|
new_user_info = self.add_new_user(
|
||||||
|
@ -336,9 +342,10 @@ class TestLDAP:
|
||||||
with pytest.raises(MoulinetteError) as exception:
|
with pytest.raises(MoulinetteError) as exception:
|
||||||
self.update_new_user(ldap_interface)
|
self.update_new_user(ldap_interface)
|
||||||
|
|
||||||
translation = m18n.g("ldap_operation_error", action="update")
|
expected_message = "error during LDAP update operation with: rdn="
|
||||||
expected_msg = translation.format(action="update")
|
expected_error = "modifications require authentication"
|
||||||
assert expected_msg in str(exception)
|
assert expected_error in str(exception)
|
||||||
|
assert expected_message in str(exception)
|
||||||
|
|
||||||
def test_anonymous_update_new_rdn(self, ldap_server):
|
def test_anonymous_update_new_rdn(self, ldap_server):
|
||||||
self.ldap_conf["parameters"]["uri"] = ldap_server.uri
|
self.ldap_conf["parameters"]["uri"] = ldap_server.uri
|
||||||
|
@ -347,9 +354,10 @@ class TestLDAP:
|
||||||
with pytest.raises(MoulinetteError) as exception:
|
with pytest.raises(MoulinetteError) as exception:
|
||||||
self.update_new_user(ldap_interface, True)
|
self.update_new_user(ldap_interface, True)
|
||||||
|
|
||||||
translation = m18n.g("ldap_operation_error", action="update")
|
expected_message = "error during LDAP update operation with: rdn="
|
||||||
expected_msg = translation.format(action="update")
|
expected_error = "modifications require authentication"
|
||||||
assert expected_msg in str(exception)
|
assert expected_error in str(exception)
|
||||||
|
assert expected_message in str(exception)
|
||||||
|
|
||||||
def test_empty_update(self, ldap_server):
|
def test_empty_update(self, ldap_server):
|
||||||
self.ldap_conf["parameters"]["uri"] = ldap_server.uri
|
self.ldap_conf["parameters"]["uri"] = ldap_server.uri
|
||||||
|
|
|
@ -115,6 +115,6 @@ def test_call_async_output_kwargs(test_file, mocker):
|
||||||
|
|
||||||
|
|
||||||
def test_check_output(test_file):
|
def test_check_output(test_file):
|
||||||
assert check_output(["cat", str(test_file)], shell=False) == "foo\nbar\n"
|
assert check_output(["cat", str(test_file)], shell=False) == "foo\nbar"
|
||||||
|
|
||||||
assert check_output("cat %s" % str(test_file)) == "foo\nbar\n"
|
assert check_output("cat %s" % str(test_file)) == "foo\nbar"
|
||||||
|
|
30
tox.ini
30
tox.ini
|
@ -1,33 +1,21 @@
|
||||||
[tox]
|
[tox]
|
||||||
envlist =
|
envlist =
|
||||||
py35
|
py{27,3}-{pytest,lint}
|
||||||
lint
|
format
|
||||||
|
format-check
|
||||||
docs
|
docs
|
||||||
skipdist = True
|
skipdist = True
|
||||||
|
|
||||||
[testenv]
|
[testenv]
|
||||||
usedevelop = True
|
usedevelop = True
|
||||||
passenv = *
|
passenv = *
|
||||||
|
extras = tests
|
||||||
deps =
|
deps =
|
||||||
pytest >= 4.6.3, < 5.0
|
py{27,3}-pytest: .[tests]
|
||||||
pytest-cov >= 2.7.1, < 3.0
|
py{27,3}-lint: flake8
|
||||||
pytest-mock >= 1.10.4, < 2.0
|
|
||||||
pytest-env >= 0.6.2, < 1.0
|
|
||||||
requests >= 2.22.0, < 3.0
|
|
||||||
requests-mock >= 1.6.0, < 2.0
|
|
||||||
toml >= 0.10, < 0.11
|
|
||||||
gevent-websocket
|
|
||||||
bottle >= 0.12
|
|
||||||
WebTest >= 2.0, < 2.1
|
|
||||||
commands =
|
commands =
|
||||||
pytest {posargs}
|
py{27,3}-pytest: pytest {posargs} -c pytest.ini
|
||||||
|
py{27,3}-lint: flake8 moulinette test
|
||||||
[testenv:lint]
|
|
||||||
commands = flake8 moulinette test
|
|
||||||
deps = flake8
|
|
||||||
skip_install = True
|
|
||||||
usedevelop = False
|
|
||||||
|
|
||||||
|
|
||||||
[testenv:format]
|
[testenv:format]
|
||||||
basepython = python3
|
basepython = python3
|
||||||
|
|
Loading…
Reference in a new issue