From 4dd1f31ce65c9eab7665d18e77c7e14ca3ec7c13 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 1 Apr 2020 19:53:21 +0200 Subject: [PATCH 01/91] Placeholder version number for 3.8 --- debian/changelog | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/debian/changelog b/debian/changelog index 2fc2ba46..14f26dc4 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,10 @@ +yunohost (3.8.0~alpha) testing; urgency=low + + Placeholder for upcoming 3.8 to avoid funky stuff with version numbers in + builds etc. + + -- Alexandre Aubin Mon, 16 Mar 2020 01:00:00 +0000 + moulinette (3.7.0.2) stable; urgency=low Bumping version number for stable release From 78f37782ae0de69abb331012f57fb37ba6cf5a4a Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 2 Apr 2020 01:30:20 +0200 Subject: [PATCH 02/91] Bypass test_mkdir_with_permission if root --- test/test_filesystem.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/test/test_filesystem.py b/test/test_filesystem.py index 6eea821f..3e4f45ac 100644 --- a/test/test_filesystem.py +++ b/test/test_filesystem.py @@ -371,6 +371,12 @@ def test_mkdir(tmp_path): def test_mkdir_with_permission(tmp_path, mocker): + + # This test only make sense when not being root + import pdb; pdb.set_trace() + if os.getuid() == 0: + return + new_path = tmp_path / "new_folder" permission = 0o700 mkdir(str(new_path), mode=permission) From 3a3ce804d6f4ac349c53b2cf1a89d2f9827dfda3 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 2 Apr 2020 23:44:15 +0200 Subject: [PATCH 03/91] Wat da fuke pytz --- moulinette/interfaces/cli.py | 3 ++- moulinette/utils/serialize.py | 4 +++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/moulinette/interfaces/cli.py b/moulinette/interfaces/cli.py index dd340398..804028bc 100644 --- a/moulinette/interfaces/cli.py +++ b/moulinette/interfaces/cli.py @@ -7,7 +7,6 @@ import locale import logging from argparse import SUPPRESS from collections import OrderedDict -import pytz from datetime import date, datetime import argcomplete @@ -103,6 +102,8 @@ def pretty_date(_date): Argument: - date -- The date or datetime to display """ + import pytz # Lazy loading, this takes like 3+ sec on a RPi2 ?! + # Deduce system timezone nowutc = datetime.now(tz=pytz.utc) nowtz = datetime.now() diff --git a/moulinette/utils/serialize.py b/moulinette/utils/serialize.py index f3119190..345cb4d4 100644 --- a/moulinette/utils/serialize.py +++ b/moulinette/utils/serialize.py @@ -1,7 +1,6 @@ import logging from json.encoder import JSONEncoder import datetime -import pytz logger = logging.getLogger("moulinette.utils.serialize") @@ -23,6 +22,9 @@ class JSONExtendedEncoder(JSONEncoder): """ def default(self, o): + + import pytz # Lazy loading, this takes like 3+ sec on a RPi2 ?! + """Return a serializable object""" # Convert compatible containers into list if isinstance(o, set) or (hasattr(o, "__iter__") and hasattr(o, "next")): From 2fba2747e3d6650f791ef32133e6f953e22755f2 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 3 Apr 2020 20:13:56 +0200 Subject: [PATCH 04/91] Drunk Aleks forgot to remove the pdb :| --- test/test_filesystem.py | 1 - 1 file changed, 1 deletion(-) diff --git a/test/test_filesystem.py b/test/test_filesystem.py index 3e4f45ac..fcfa832b 100644 --- a/test/test_filesystem.py +++ b/test/test_filesystem.py @@ -373,7 +373,6 @@ def test_mkdir(tmp_path): def test_mkdir_with_permission(tmp_path, mocker): # This test only make sense when not being root - import pdb; pdb.set_trace() if os.getuid() == 0: return From 8c7b6c0e5f8ce029a51e197a9cf55a4d979bea68 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 2 Apr 2020 23:44:15 +0200 Subject: [PATCH 05/91] Wat da fuke pytz --- moulinette/interfaces/cli.py | 3 ++- moulinette/utils/serialize.py | 4 +++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/moulinette/interfaces/cli.py b/moulinette/interfaces/cli.py index 70e2833a..1283bd93 100644 --- a/moulinette/interfaces/cli.py +++ b/moulinette/interfaces/cli.py @@ -7,7 +7,6 @@ import locale import logging from argparse import SUPPRESS from collections import OrderedDict -import pytz from datetime import date, datetime import argcomplete @@ -101,6 +100,8 @@ def pretty_date(_date): Argument: - date -- The date or datetime to display """ + import pytz # Lazy loading, this takes like 3+ sec on a RPi2 ?! + # Deduce system timezone nowutc = datetime.now(tz=pytz.utc) nowtz = datetime.now() diff --git a/moulinette/utils/serialize.py b/moulinette/utils/serialize.py index a4492cba..fab1114f 100644 --- a/moulinette/utils/serialize.py +++ b/moulinette/utils/serialize.py @@ -1,7 +1,6 @@ import logging from json.encoder import JSONEncoder import datetime -import pytz logger = logging.getLogger('moulinette.utils.serialize') @@ -22,6 +21,9 @@ class JSONExtendedEncoder(JSONEncoder): """ def default(self, o): + + import pytz # Lazy loading, this takes like 3+ sec on a RPi2 ?! + """Return a serializable object""" # Convert compatible containers into list if isinstance(o, set) or ( From 6391ef23c4816ba6a24ecc0567f5b177457bdd62 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 9 Apr 2020 15:00:21 +0200 Subject: [PATCH 06/91] Update changelog for 3.7.1 --- debian/changelog | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/debian/changelog b/debian/changelog index 2fc2ba46..ae0263d2 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,9 @@ +moulinette (3.7.1) stable; urgency=low + + - [enh] Lazy loading pytz for performances + + -- Alexandre Aubin Thu, 9 April 2020 14:55:00 +0000 + moulinette (3.7.0.2) stable; urgency=low Bumping version number for stable release From 628ffc9da122c84ac49e15ab728410d3a6c77f87 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 9 Apr 2020 19:05:20 +0200 Subject: [PATCH 07/91] Report the actual error when ldap fails --- locales/en.json | 1 - moulinette/authenticators/ldap.py | 41 ++++++++++++------------------- 2 files changed, 16 insertions(+), 26 deletions(-) diff --git a/locales/en.json b/locales/en.json index 5c55d14c..97ff034f 100644 --- a/locales/en.json +++ b/locales/en.json @@ -19,7 +19,6 @@ "invalid_token": "Invalid token - please authenticate", "invalid_usage": "Invalid usage, pass --help to see help", "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", "logged_in": "Logged in", "logged_out": "Logged out", diff --git a/moulinette/authenticators/ldap.py b/moulinette/authenticators/ldap.py index 312b75f0..1e990c0f 100644 --- a/moulinette/authenticators/ldap.py +++ b/moulinette/authenticators/ldap.py @@ -148,15 +148,12 @@ class Authenticator(BaseAuthenticator): try: result = self.con.search_s(base, ldap.SCOPE_SUBTREE, filter, attrs) except Exception as e: - logger.exception( + raise MoulinetteError( "error during LDAP search operation with: base='%s', " - "filter='%s', attrs=%s and exception %s", - base, - filter, - attrs, - e, + "filter='%s', attrs=%s and exception %s" + % (base, filter, attrs, e), + raw_msg=True ) - raise MoulinetteError("ldap_operation_error", action="search") result_list = [] if not attrs or "dn" not in attrs: @@ -185,14 +182,12 @@ class Authenticator(BaseAuthenticator): try: self.con.add_s(dn, ldif) except Exception as e: - logger.exception( + raise MoulinetteError( "error during LDAP add operation with: rdn='%s', " - "attr_dict=%s and exception %s", - rdn, - attr_dict, - e, + "attr_dict=%s and exception %s" + % (rdn, attr_dict, e), + raw_msg=True ) - raise MoulinetteError("ldap_operation_error", action="add") else: return True @@ -211,12 +206,11 @@ class Authenticator(BaseAuthenticator): try: self.con.delete_s(dn) except Exception as e: - logger.exception( - "error during LDAP delete operation with: rdn='%s' and exception %s", - rdn, - e, + raise MoulinetteError( + "error during LDAP delete operation with: rdn='%s' and exception %s" + % (rdn, e), + raw_msg=True ) - raise MoulinetteError("ldap_operation_error", action="remove") else: return True @@ -249,15 +243,12 @@ class Authenticator(BaseAuthenticator): self.con.modify_ext_s(dn, ldif) except Exception as e: - logger.exception( + raise MoulinetteError( "error during LDAP update operation with: rdn='%s', " - "attr_dict=%s, new_rdn=%s and exception: %s", - rdn, - attr_dict, - new_rdn, - e, + "attr_dict=%s, new_rdn=%s and exception: %s" + % (rdn, attr_dict, new_rdn, e), + raw_msg=True ) - raise MoulinetteError("ldap_operation_error", action="update") else: return True From cbc776aedb3c0f8e8b720b3de8cf290b3d017ed9 Mon Sep 17 00:00:00 2001 From: kay0u Date: Thu, 9 Apr 2020 20:32:48 +0000 Subject: [PATCH 08/91] Update changelog for 3.8.0 release --- debian/changelog | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/debian/changelog b/debian/changelog index 65f6782d..b24f0c33 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,15 +1,30 @@ -yunohost (3.8.0~alpha) testing; urgency=low +moulinette (3.8.0) testing; urgency=low - Placeholder for upcoming 3.8 to avoid funky stuff with version numbers in - builds etc. + # Major stuff - -- Alexandre Aubin Mon, 16 Mar 2020 01:00:00 +0000 + - 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 Thu, 09 Apr 2020 20:29:48 +0000 moulinette (3.7.1) stable; urgency=low - [enh] Lazy loading pytz for performances - -- Alexandre Aubin Thu, 9 April 2020 14:55:00 +0000 + -- Alexandre Aubin Thu, 9 Apr 2020 14:55:00 +0000 moulinette (3.7.0.2) stable; urgency=low From 0f37a70f27acf0ad9ac1645f695617b03802b894 Mon Sep 17 00:00:00 2001 From: Kay0u Date: Fri, 10 Apr 2020 00:12:19 +0200 Subject: [PATCH 09/91] Fix tests --- locales/ar.json | 1 - locales/ca.json | 1 - locales/cmn.json | 1 - locales/de.json | 1 - locales/eo.json | 1 - locales/es.json | 1 - locales/fr.json | 1 - locales/hi.json | 1 - locales/it.json | 1 - locales/nl.json | 1 - locales/oc.json | 1 - locales/pl.json | 1 - locales/pt.json | 1 - locales/ru.json | 1 - locales/sv.json | 1 - locales/tr.json | 1 - moulinette/authenticators/ldap.py | 14 ++++++------- test/test_ldap.py | 35 ++++++++++++++++++------------- 18 files changed, 26 insertions(+), 39 deletions(-) diff --git a/locales/ar.json b/locales/ar.json index d2853799..260d614f 100644 --- a/locales/ar.json +++ b/locales/ar.json @@ -18,7 +18,6 @@ "invalid_password": "كلمة السر خاطئة", "invalid_usage": "إستعمال غير صالح، إستخدم --help لعرض المساعدة", "ldap_attribute_already_exists": "الخاصية '{attribute}' موجودة مسبقا و تحمل القيمة '{value}'", - "ldap_operation_error": "طرأ هناك خطأ أثناء عملية في LDAP", "ldap_server_down": "لا يمكن الإتصال بخادم LDAP", "logged_in": "مُتّصل", "logged_out": "تم تسجيل خروجك", diff --git a/locales/ca.json b/locales/ca.json index c0f1e040..bde04548 100644 --- a/locales/ca.json +++ b/locales/ca.json @@ -18,7 +18,6 @@ "invalid_password": "Contrasenya invàlida", "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_operation_error": "Hi ha hagut un error durant l'operació de LDAP", "ldap_server_down": "No s'ha pogut connectar amb el servidor LDAP", "logged_in": "Sessió iniciada", "logged_out": "Sessió tancada", diff --git a/locales/cmn.json b/locales/cmn.json index 634868a4..e9b8b52d 100644 --- a/locales/cmn.json +++ b/locales/cmn.json @@ -19,7 +19,6 @@ "invalid_password": "密码错误", "invalid_usage": "用法错误,输入 --help 查看帮助信息", "ldap_attribute_already_exists": "参数{attribute}已赋值{value}", - "ldap_operation_error": "LDAP操作时发生了错误", "ldap_server_down": "无法连接LDAP服务器", "logged_in": "登录成功", "logged_out": "登出", diff --git a/locales/de.json b/locales/de.json index 0b9f409f..c779dbfd 100644 --- a/locales/de.json +++ b/locales/de.json @@ -16,7 +16,6 @@ "invalid_password": "Passwort falsch", "invalid_usage": "Falscher Aufruf, verwende --help für den Hilfstext", "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", "logged_in": "Angemeldet", "logged_out": "Abgemeldet", diff --git a/locales/eo.json b/locales/eo.json index d0f3ebd9..45435701 100644 --- a/locales/eo.json +++ b/locales/eo.json @@ -34,7 +34,6 @@ "not_logged_in": "Vi ne estas ensalutinta", "logged_in": "Ensalutinta", "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}'", "invalid_usage": "Nevalida uzado, preterpase '--help' por vidi helpon", "invalid_password": "Nevalida pasvorto", diff --git a/locales/es.json b/locales/es.json index 0e18bb58..399ad276 100644 --- a/locales/es.json +++ b/locales/es.json @@ -18,7 +18,6 @@ "invalid_password": "Contraseña no válida", "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_operation_error": "Ha ocurrido un error durante la operación de LDAP", "ldap_server_down": "No se pudo conectar con el servidor LDAP", "logged_in": "Sesión iniciada", "logged_out": "Sesión cerrada", diff --git a/locales/fr.json b/locales/fr.json index b0f63470..a30d4bab 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -18,7 +18,6 @@ "invalid_password": "Mot de passe incorrect", "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_operation_error": "Une erreur est survenue lors de l’opération LDAP", "ldap_server_down": "Impossible d’atteindre le serveur LDAP", "logged_in": "Connecté", "logged_out": "Déconnecté", diff --git a/locales/hi.json b/locales/hi.json index 5f98ea16..8ddc1a11 100644 --- a/locales/hi.json +++ b/locales/hi.json @@ -18,7 +18,6 @@ "invalid_password": "अवैध पासवर्ड", "invalid_usage": "अवैध उपयोग, सहायता देखने के लिए --help साथ लिखे।", "ldap_attribute_already_exists": "'{attribute}' तर्क पहले इस वैल्यू '{value}' से मौजूद है।", - "ldap_operation_error": "LDAP ऑपरेशन के दौरान त्रुटि हो गई है।", "ldap_server_down": "LDAP सर्वर तक पहुंचने में असमर्थ।", "logged_in": "लोग्ड इन", "logged_out": "लॉग आउट", diff --git a/locales/it.json b/locales/it.json index c7fc2864..fa228abc 100644 --- a/locales/it.json +++ b/locales/it.json @@ -20,7 +20,6 @@ "invalid_password": "Password non valida", "invalid_usage": "Utilizzo non valido, usa --help per vedere l'aiuto", "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", "logged_in": "Connesso", "not_logged_in": "Non hai effettuato l'accesso", diff --git a/locales/nl.json b/locales/nl.json index cbbee8cc..df4fb116 100644 --- a/locales/nl.json +++ b/locales/nl.json @@ -16,7 +16,6 @@ "invalid_password": "Ongeldig wachtwoord", "invalid_usage": "Ongeldig gebruik, doe --help om de hulptekst te lezen", "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", "logged_in": "Ingelogd", "logged_out": "Uitgelogd", diff --git a/locales/oc.json b/locales/oc.json index f2de095f..5db9677f 100644 --- a/locales/oc.json +++ b/locales/oc.json @@ -31,7 +31,6 @@ "warning": "Atencion :", "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_operation_error": "Una error s’es producha pendent l’operacion LDAP", "operation_interrupted": "Operacion interrompuda", "server_already_running": "Un servidor es ja en execucion sus aqueste pòrt", "success": "Capitada !", diff --git a/locales/pl.json b/locales/pl.json index d02a6f65..e816f4b6 100644 --- a/locales/pl.json +++ b/locales/pl.json @@ -34,7 +34,6 @@ "not_logged_in": "Nie jesteś zalogowany", "logged_in": "Zalogowany", "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}”", "invalid_usage": "Nieprawidłowe użycie. Przejdź --help, aby wyświetlić pomoc", "invalid_token": "Nieprawidłowy token - proszę uwierzytelnić", diff --git a/locales/pt.json b/locales/pt.json index 97c96a61..a7e70aea 100644 --- a/locales/pt.json +++ b/locales/pt.json @@ -15,7 +15,6 @@ "invalid_password": "Senha incorreta", "invalid_usage": "Uso invalido, utilizar --help para ver a ajuda", "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", "logged_in": "Sessão iniciada", "logged_out": "Sessão terminada", diff --git a/locales/ru.json b/locales/ru.json index 03465b52..e22cbc65 100644 --- a/locales/ru.json +++ b/locales/ru.json @@ -41,7 +41,6 @@ "download_timeout": "Превышено время ожидания ответа от {url:s}.", "download_unknown_error": "Ошибка при загрузке данных с {url:s} : {error:s}", "instance_already_running": "Операция YunoHost уже запущена. Пожалуйста, подождите, пока он закончится, прежде чем запускать другой.", - "ldap_operation_error": "Ошибка в процессе работы LDAP", "root_required": "Чтобы выполнить это действие, вы должны иметь права root", "corrupted_json": "Повреждённый json получен от {ressource:s} (причина: {error:s})", "command_unknown": "Команда '{command:s}' неизвестна ?", diff --git a/locales/sv.json b/locales/sv.json index eee34d09..d4a80f60 100644 --- a/locales/sv.json +++ b/locales/sv.json @@ -49,7 +49,6 @@ "unable_retrieve_session": "Det gick inte att hämta sessionen eftersom '{exception}'", "unable_authenticate": "Det går inte att verifiera", "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_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.", diff --git a/locales/tr.json b/locales/tr.json index 7e6673e1..2b89424c 100644 --- a/locales/tr.json +++ b/locales/tr.json @@ -11,7 +11,6 @@ "invalid_argument": "Geçersiz argüman '{argument}': {error}", "invalid_password": "Geçersiz parola", "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", "logged_in": "Giriş yapıldı", "logged_out": "Çıkış yapıldı", diff --git a/moulinette/authenticators/ldap.py b/moulinette/authenticators/ldap.py index 1e990c0f..2683772d 100644 --- a/moulinette/authenticators/ldap.py +++ b/moulinette/authenticators/ldap.py @@ -150,9 +150,8 @@ class Authenticator(BaseAuthenticator): except Exception as e: raise MoulinetteError( "error during LDAP search operation with: base='%s', " - "filter='%s', attrs=%s and exception %s" - % (base, filter, attrs, e), - raw_msg=True + "filter='%s', attrs=%s and exception %s" % (base, filter, attrs, e), + raw_msg=True, ) result_list = [] @@ -184,9 +183,8 @@ class Authenticator(BaseAuthenticator): except Exception as e: raise MoulinetteError( "error during LDAP add operation with: rdn='%s', " - "attr_dict=%s and exception %s" - % (rdn, attr_dict, e), - raw_msg=True + "attr_dict=%s and exception %s" % (rdn, attr_dict, e), + raw_msg=True, ) else: return True @@ -209,7 +207,7 @@ class Authenticator(BaseAuthenticator): raise MoulinetteError( "error during LDAP delete operation with: rdn='%s' and exception %s" % (rdn, e), - raw_msg=True + raw_msg=True, ) else: return True @@ -247,7 +245,7 @@ class Authenticator(BaseAuthenticator): "error during LDAP update operation with: rdn='%s', " "attr_dict=%s, new_rdn=%s and exception: %s" % (rdn, attr_dict, new_rdn, e), - raw_msg=True + raw_msg=True, ) else: return True diff --git a/test/test_ldap.py b/test/test_ldap.py index fd1fcb4e..fadff9e6 100644 --- a/test/test_ldap.py +++ b/test/test_ldap.py @@ -208,9 +208,10 @@ class TestLDAP: with pytest.raises(MoulinetteError) as exception: self.add_new_user(ldap_interface) - translation = m18n.g("ldap_operation_error", action="add") - expected_msg = translation.format(action="add") - assert expected_msg in str(exception) + expected_message = "error during LDAP add operation with: rdn=" + expected_error = "modifications require authentication" + assert expected_error in str(exception) + assert expected_message in str(exception) def remove_new_user(self, ldap_interface): new_user_info = self.add_new_user( @@ -229,9 +230,10 @@ class TestLDAP: "uid=%s,ou=users,dc=yunohost,dc=org" % uid, attrs=None ) - translation = m18n.g("ldap_operation_error", action="search") - expected_msg = translation.format(action="search") - assert expected_msg in str(exception) + expected_message = "error during LDAP search operation with: base=" + expected_error = "No such object" + assert expected_error in str(exception) + assert expected_message in str(exception) def test_admin_remove(self, ldap_server): self.ldap_conf["parameters"]["uri"] = ldap_server.uri @@ -257,9 +259,10 @@ class TestLDAP: with pytest.raises(MoulinetteError) as exception: self.remove_new_user(ldap_interface) - translation = m18n.g("ldap_operation_error", action="remove") - expected_msg = translation.format(action="remove") - assert expected_msg in str(exception) + expected_message = "error during LDAP delete operation with: rdn=" + expected_error = "modifications require authentication" + assert expected_error in str(exception) + assert expected_message in str(exception) def update_new_user(self, ldap_interface, new_rdn=False): new_user_info = self.add_new_user( @@ -336,9 +339,10 @@ class TestLDAP: with pytest.raises(MoulinetteError) as exception: self.update_new_user(ldap_interface) - translation = m18n.g("ldap_operation_error", action="update") - expected_msg = translation.format(action="update") - assert expected_msg in str(exception) + expected_message = "error during LDAP update operation with: rdn=" + expected_error = "modifications require authentication" + assert expected_error in str(exception) + assert expected_message in str(exception) def test_anonymous_update_new_rdn(self, ldap_server): self.ldap_conf["parameters"]["uri"] = ldap_server.uri @@ -347,9 +351,10 @@ class TestLDAP: with pytest.raises(MoulinetteError) as exception: self.update_new_user(ldap_interface, True) - translation = m18n.g("ldap_operation_error", action="update") - expected_msg = translation.format(action="update") - assert expected_msg in str(exception) + expected_message = "error during LDAP update operation with: rdn=" + expected_error = "modifications require authentication" + assert expected_error in str(exception) + assert expected_message in str(exception) def test_empty_update(self, ldap_server): self.ldap_conf["parameters"]["uri"] = ldap_server.uri From d94832ec3d1ba8236bbc75ed1283d232fe2c8067 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 9 Apr 2020 19:05:20 +0200 Subject: [PATCH 10/91] Report the actual error when ldap fails --- locales/en.json | 1 - moulinette/authenticators/ldap.py | 35 ++++++++++++++++++++----------- 2 files changed, 23 insertions(+), 13 deletions(-) diff --git a/locales/en.json b/locales/en.json index 36d93a8f..09390047 100644 --- a/locales/en.json +++ b/locales/en.json @@ -19,7 +19,6 @@ "invalid_password": "Invalid password", "invalid_usage": "Invalid usage, pass --help to see help", "ldap_attribute_already_exists": "Attribute '{attribute}' already exists with value '{value}'", - "ldap_operation_error": "An error occurred during LDAP operation", "ldap_server_down": "Unable to reach LDAP server", "logged_in": "Logged in", "logged_out": "Logged out", diff --git a/moulinette/authenticators/ldap.py b/moulinette/authenticators/ldap.py index 15834678..31bafb4a 100644 --- a/moulinette/authenticators/ldap.py +++ b/moulinette/authenticators/ldap.py @@ -141,9 +141,12 @@ class Authenticator(BaseAuthenticator): try: result = self.con.search_s(base, ldap.SCOPE_SUBTREE, filter, attrs) except Exception as e: - logger.exception("error during LDAP search operation with: base='%s', " - "filter='%s', attrs=%s and exception %s", base, filter, attrs, e) - raise MoulinetteError('ldap_operation_error') + raise MoulinetteError( + "error during LDAP search operation with: base='%s', " + "filter='%s', attrs=%s and exception %s" + % (base, filter, attrs, e), + raw_msg=True + ) result_list = [] if not attrs or 'dn' not in attrs: @@ -172,9 +175,12 @@ class Authenticator(BaseAuthenticator): try: self.con.add_s(dn, ldif) except Exception as e: - logger.exception("error during LDAP add operation with: rdn='%s', " - "attr_dict=%s and exception %s", rdn, attr_dict, e) - raise MoulinetteError('ldap_operation_error') + raise MoulinetteError( + "error during LDAP add operation with: rdn='%s', " + "attr_dict=%s and exception %s" + % (rdn, attr_dict, e), + raw_msg=True + ) else: return True @@ -193,8 +199,11 @@ class Authenticator(BaseAuthenticator): try: self.con.delete_s(dn) except Exception as e: - logger.exception("error during LDAP delete operation with: rdn='%s' and exception %s", rdn, e) - raise MoulinetteError('ldap_operation_error') + raise MoulinetteError( + "error during LDAP delete operation with: rdn='%s' and exception %s" + % (rdn, e), + raw_msg=True + ) else: return True @@ -226,10 +235,12 @@ class Authenticator(BaseAuthenticator): self.con.modify_ext_s(dn, ldif) except Exception as e: - logger.exception("error during LDAP update operation with: rdn='%s', " - "attr_dict=%s, new_rdn=%s and exception: %s", rdn, attr_dict, - new_rdn, e) - raise MoulinetteError('ldap_operation_error') + raise MoulinetteError( + "error during LDAP update operation with: rdn='%s', " + "attr_dict=%s, new_rdn=%s and exception: %s" + % (rdn, attr_dict, new_rdn, e), + raw_msg=True + ) else: return True From f5fcefdce69c9703118a7f10a9e854567e37ef80 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 17 Apr 2020 16:59:07 +0200 Subject: [PATCH 11/91] Update changelog for 3.7.1.1 --- debian/changelog | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/debian/changelog b/debian/changelog index ae0263d2..49d40954 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,8 +1,15 @@ +moulinette (3.7.1.1) stable; urgency=low + + - [fix] Report actual errors when some LDAP operation fails to ease + debugging + + -- Alexandre Aubin Fri, 17 Apr 2020 17:00:00 +0000 + moulinette (3.7.1) stable; urgency=low - [enh] Lazy loading pytz for performances - -- Alexandre Aubin Thu, 9 April 2020 14:55:00 +0000 + -- Alexandre Aubin Thu, 9 Apr 2020 14:55:00 +0000 moulinette (3.7.0.2) stable; urgency=low From 6f1133b205aa22f3360d06673215e2ad3506e827 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 23 Apr 2020 04:26:00 +0200 Subject: [PATCH 12/91] The hell with 'pythonic try/except' ... Just use a goddamn if/else for that kind of stuff --- moulinette/interfaces/api.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/moulinette/interfaces/api.py b/moulinette/interfaces/api.py index 98a95c1d..ee3757bc 100644 --- a/moulinette/interfaces/api.py +++ b/moulinette/interfaces/api.py @@ -327,11 +327,10 @@ class _ActionsMapPlugin(object): # Append other request params for k, v in request.params.dict.items(): v = _format(v) - try: - curr_v = params[k] - except KeyError: + if k not in params.keys(): params[k] = v else: + curr_v = params[k] # Append param value to the list if not isinstance(curr_v, list): curr_v = [curr_v] From 0033c6f0ec1ff326c43f34d60a973cdd6106ed5f Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 25 Apr 2020 17:17:45 +0200 Subject: [PATCH 13/91] try/catch around request.get_cookie to catch stupid CookieError --- moulinette/interfaces/api.py | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/moulinette/interfaces/api.py b/moulinette/interfaces/api.py index 98a95c1d..38a0f98b 100644 --- a/moulinette/interfaces/api.py +++ b/moulinette/interfaces/api.py @@ -362,13 +362,24 @@ class _ActionsMapPlugin(object): """ # 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: s_secret = self.secrets[s_id] except KeyError: s_tokens = {} 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() try: From 1049a28d6a76e185662d5c38f691cb3ee737159b Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 25 Apr 2020 17:29:27 +0200 Subject: [PATCH 14/91] Computer ain't happy because *SPAECIZE* --- moulinette/interfaces/api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/moulinette/interfaces/api.py b/moulinette/interfaces/api.py index 38a0f98b..0f5ebaca 100644 --- a/moulinette/interfaces/api.py +++ b/moulinette/interfaces/api.py @@ -369,7 +369,7 @@ class _ActionsMapPlugin(object): # 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: s_secret = self.secrets[s_id] except KeyError: From 0e6c35522b82efc70e286ae60b3aba76600819ba Mon Sep 17 00:00:00 2001 From: Zeik0s Date: Wed, 15 Apr 2020 18:43:04 +0000 Subject: [PATCH 15/91] Translated using Weblate (German) Currently translated at 74.5% (41 of 55 strings) Translation: YunoHost/moulinette Translate-URL: https://translate.yunohost.org/projects/yunohost/moulinette/de/ --- locales/de.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/locales/de.json b/locales/de.json index c779dbfd..715c7727 100644 --- a/locales/de.json +++ b/locales/de.json @@ -36,5 +36,6 @@ "deprecated_command_alias": "'{prog} {old}' ist veraltet und wird bald entfernt werden, benutze '{prog} {new}' stattdessen", "unknown_group": "Gruppe '{group}' ist unbekannt", "unknown_user": "Benutzer '{user}' ist unbekannt", - "info": "Info:" + "info": "Info:", + "invalid_token": "Ungültiger Token - bitte authentifizieren" } From 5d8cafcb86839f9f48303961e2057e3218bdd435 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20D=C3=B6ring?= Date: Wed, 15 Apr 2020 18:43:41 +0000 Subject: [PATCH 16/91] Translated using Weblate (German) Currently translated at 74.5% (41 of 55 strings) Translation: YunoHost/moulinette Translate-URL: https://translate.yunohost.org/projects/yunohost/moulinette/de/ --- locales/de.json | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/locales/de.json b/locales/de.json index 715c7727..c1aa1346 100644 --- a/locales/de.json +++ b/locales/de.json @@ -37,5 +37,9 @@ "unknown_group": "Gruppe '{group}' ist unbekannt", "unknown_user": "Benutzer '{user}' ist unbekannt", "info": "Info:", - "invalid_token": "Ungültiger Token - bitte authentifizieren" + "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})" } From 2195c039c598fe5d805bda6eb313e2f25d11630f Mon Sep 17 00:00:00 2001 From: Zeik0s Date: Wed, 15 Apr 2020 18:44:23 +0000 Subject: [PATCH 17/91] Translated using Weblate (German) Currently translated at 76.4% (42 of 55 strings) Translation: YunoHost/moulinette Translate-URL: https://translate.yunohost.org/projects/yunohost/moulinette/de/ --- locales/de.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/locales/de.json b/locales/de.json index c1aa1346..6a6f2a19 100644 --- a/locales/de.json +++ b/locales/de.json @@ -38,7 +38,7 @@ "unknown_user": "Benutzer '{user}' ist unbekannt", "info": "Info:", "invalid_token": "Ungültiger Token - bitte authentifizieren", - "corrupted_json": "Beschädigtes JSON gelesen von {ressource:s} (reason: {error:s})", + "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})" From 778bb20fc087e6a8b1eb9434d6f61630f448a366 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20D=C3=B6ring?= Date: Wed, 15 Apr 2020 18:45:17 +0000 Subject: [PATCH 18/91] Translated using Weblate (German) Currently translated at 76.4% (42 of 55 strings) Translation: YunoHost/moulinette Translate-URL: https://translate.yunohost.org/projects/yunohost/moulinette/de/ --- locales/de.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/locales/de.json b/locales/de.json index 6a6f2a19..01493e3e 100644 --- a/locales/de.json +++ b/locales/de.json @@ -41,5 +41,6 @@ "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})" + "cannot_open_file": "Kann Datei {file:s} nicht öffnen (reason: {error:s})", + "corrupted_yaml": "Beschädigtes JAML gelesen von {ressource:s} (reason: {error:s})" } From e9c171beff49b8678e91e3fd0774afdbda7a33e9 Mon Sep 17 00:00:00 2001 From: Zeik0s Date: Wed, 15 Apr 2020 18:45:29 +0000 Subject: [PATCH 19/91] Translated using Weblate (German) Currently translated at 100.0% (55 of 55 strings) Translation: YunoHost/moulinette Translate-URL: https://translate.yunohost.org/projects/yunohost/moulinette/de/ --- locales/de.json | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/locales/de.json b/locales/de.json index 01493e3e..f5caa564 100644 --- a/locales/de.json +++ b/locales/de.json @@ -42,5 +42,15 @@ "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 JAML gelesen von {ressource:s} (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}" } From 94fb6ccf6849827e0a29e4c9f81bedf950fa4f6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20D=C3=B6ring?= Date: Wed, 15 Apr 2020 18:45:40 +0000 Subject: [PATCH 20/91] Translated using Weblate (German) Currently translated at 100.0% (55 of 55 strings) Translation: YunoHost/moulinette Translate-URL: https://translate.yunohost.org/projects/yunohost/moulinette/de/ --- locales/de.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/locales/de.json b/locales/de.json index f5caa564..37496075 100644 --- a/locales/de.json +++ b/locales/de.json @@ -52,5 +52,8 @@ "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_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})" } From 4dfcd3a13d2634b167882a354b54983924afac41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89ric=20Gaspar?= Date: Wed, 15 Apr 2020 16:44:17 +0000 Subject: [PATCH 21/91] Translated using Weblate (French) Currently translated at 100.0% (55 of 55 strings) Translation: YunoHost/moulinette Translate-URL: https://translate.yunohost.org/projects/yunohost/moulinette/fr/ --- locales/fr.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/locales/fr.json b/locales/fr.json index a30d4bab..b589d621 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -54,6 +54,6 @@ "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_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": "l’autre commande vient de se terminer, lancement de cette commande", "invalid_token": "Jeton non valide - veuillez vous authentifier" } From a495d373d42457305f84b9f0f47c380a20432fd2 Mon Sep 17 00:00:00 2001 From: amirale qt Date: Mon, 20 Apr 2020 10:47:37 +0000 Subject: [PATCH 22/91] Translated using Weblate (Esperanto) Currently translated at 100.0% (55 of 55 strings) Translation: YunoHost/moulinette Translate-URL: https://translate.yunohost.org/projects/yunohost/moulinette/eo/ --- locales/eo.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/locales/eo.json b/locales/eo.json index 45435701..2d45a9da 100644 --- a/locales/eo.json +++ b/locales/eo.json @@ -1,7 +1,7 @@ { "password": "Pasvorto", "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": "Alia komando de YunoHost funkcias ĝuste nun, ni atendas, ke ĝi finiĝos antaŭ ol funkcii ĉi tiu", "command_unknown": "Komando '{command:s}' nekonata?", From f7c9fdebfa5a610525506677b4aa40f945ad45bf Mon Sep 17 00:00:00 2001 From: amirale qt Date: Mon, 20 Apr 2020 09:04:10 +0000 Subject: [PATCH 23/91] Translated using Weblate (Dutch) Currently translated at 100.0% (55 of 55 strings) Translation: YunoHost/moulinette Translate-URL: https://translate.yunohost.org/projects/yunohost/moulinette/nl/ --- locales/nl.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/locales/nl.json b/locales/nl.json index df4fb116..1434824b 100644 --- a/locales/nl.json +++ b/locales/nl.json @@ -49,7 +49,7 @@ "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}", "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": "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})", From 7a093b90d73948e0275afeb8589b8884e2640b67 Mon Sep 17 00:00:00 2001 From: amirale qt Date: Mon, 20 Apr 2020 08:59:59 +0000 Subject: [PATCH 24/91] Translated using Weblate (Polish) Currently translated at 100.0% (55 of 55 strings) Translation: YunoHost/moulinette Translate-URL: https://translate.yunohost.org/projects/yunohost/moulinette/pl/ --- locales/pl.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/locales/pl.json b/locales/pl.json index e816f4b6..2d97b0aa 100644 --- a/locales/pl.json +++ b/locales/pl.json @@ -1,7 +1,7 @@ { "logged_out": "Wylogowano", - "password": "hasło", - "warn_the_user_that_lock_is_acquired": "drugie polecenie właśnie się zakończyło, teraz uruchamia to polecenie", + "password": "Hasło", + "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": "Kolejne polecenie YunoHost jest teraz uruchomione, czekamy na jego zakończenie przed uruchomieniem tego", "command_unknown": "Polecenie „{command:s}” jest nieznane?", From 2673b4602b3ce1a5b7f2116fd3db6e1a6a766fe4 Mon Sep 17 00:00:00 2001 From: amirale qt Date: Mon, 20 Apr 2020 09:06:22 +0000 Subject: [PATCH 25/91] Translated using Weblate (Nepali) Currently translated at 3.6% (2 of 55 strings) Translation: YunoHost/moulinette Translate-URL: https://translate.yunohost.org/projects/yunohost/moulinette/ne/ --- locales/ne.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/locales/ne.json b/locales/ne.json index 0967ef42..3f07aa14 100644 --- a/locales/ne.json +++ b/locales/ne.json @@ -1 +1,4 @@ -{} +{ + "logged_out": "लग आउट", + "password": "पासवर्ड" +} From b42ed7be7acf62bc79a158088298e43d4140c5d5 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 30 Apr 2020 17:59:47 +0200 Subject: [PATCH 26/91] Less spooky messages when session expired --- locales/en.json | 1 + moulinette/authenticators/__init__.py | 8 +++++++- moulinette/cache.py | 7 +++++++ 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/locales/en.json b/locales/en.json index 97ff034f..aa27c5cd 100644 --- a/locales/en.json +++ b/locales/en.json @@ -31,6 +31,7 @@ "success": "Success!", "unable_authenticate": "Unable to authenticate", "unable_retrieve_session": "Unable to retrieve the session because '{exception}'", + "session_expired": "The session expired. Please re-authenticate.", "unknown_group": "Unknown '{group}' group", "unknown_user": "Unknown '{user}' user", "values_mismatch": "Values don't match", diff --git a/moulinette/authenticators/__init__.py b/moulinette/authenticators/__init__.py index 3a78b7e6..fa0feb45 100644 --- a/moulinette/authenticators/__init__.py +++ b/moulinette/authenticators/__init__.py @@ -5,7 +5,7 @@ import logging import hashlib 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 logger = logging.getLogger("moulinette.authenticator") @@ -159,6 +159,10 @@ class BaseAuthenticator(object): "%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): """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): """Checks session and token against the stored session token""" + if not self._session_exists(self, session_id): + raise MoulinetteError("session_expired") try: # FIXME : shouldn't we also add a check that this session file # is not too old ? e.g. not older than 24 hours ? idk... diff --git a/moulinette/cache.py b/moulinette/cache.py index f71c3fca..c6c8df5e 100644 --- a/moulinette/cache.py +++ b/moulinette/cache.py @@ -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) file_path = os.path.join(cache_dir, filename) 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) From eb6d56f7ab6ffc3c06a199fb77b9924f5443c9de Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 1 May 2020 01:08:10 +0200 Subject: [PATCH 27/91] Simplify(?) interface initialization --- moulinette/__init__.py | 35 +++++++++++++++--------------- moulinette/core.py | 49 ------------------------------------------ 2 files changed, 17 insertions(+), 67 deletions(-) diff --git a/moulinette/__init__.py b/moulinette/__init__.py index 4aae1be0..d9df19b1 100755 --- a/moulinette/__init__.py +++ b/moulinette/__init__.py @@ -1,7 +1,6 @@ # -*- coding: utf-8 -*- from moulinette.core import ( - init_interface, MoulinetteError, MoulinetteSignals, Moulinette18n, @@ -73,8 +72,6 @@ def init(logging_config=None, **kwargs): # Easy access to interfaces - - def api( namespaces, host="localhost", port=80, routes={}, use_websocket=True, use_cache=True ): @@ -93,13 +90,16 @@ def api( instead of using the cached one """ + from moulinette.actionsmap import ActionsMap + from moulinette.interfaces.api import Interface, ActionsMapParser try: - moulinette = init_interface( - "api", - kwargs={"routes": routes, "use_websocket": use_websocket}, - actionsmap={"namespaces": namespaces, "use_cache": use_cache}, - ) - moulinette.run(host, port) + actionsmap = ActionsMap(ActionsMapParser, + namespaces=namespaces, + use_cache=use_cache) + interface = Interface(actionsmap=actionsmap, + routes=routes, + use_websocket=use_websocket) + interface.run(host, port) except MoulinetteError as e: import logging @@ -138,16 +138,15 @@ def cli( class at construction """ + from moulinette.actionsmap import ActionsMap + from moulinette.interfaces.cli import Interface, ActionsMapParser try: - moulinette = init_interface( - "cli", - actionsmap={ - "namespaces": namespaces, - "use_cache": use_cache, - "parser_kwargs": parser_kwargs, - }, - ) - moulinette.run(args, output_as=output_as, password=password, timeout=timeout) + actionsmap = ActionsMap(ActionsMapParser, + namespaces=namespaces, + use_cache=use_cache, + parser_kwargs=parser_kwargs) + interface = Interface(actionsmap=actionsmap) + interface.run(args, output_as=output_as, password=password, timeout=timeout) except MoulinetteError as e: import logging diff --git a/moulinette/core.py b/moulinette/core.py index d3a2299c..f7ef4876 100644 --- a/moulinette/core.py +++ b/moulinette/core.py @@ -5,8 +5,6 @@ import time import json import logging -from importlib import import_module - import moulinette from moulinette.globals import init_moulinette_env @@ -375,53 +373,6 @@ class MoulinetteSignals(object): 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 ---------------------------------------------- From b6258de2dbea7bc973b17698a41f148ad291dec4 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 1 May 2020 01:13:40 +0200 Subject: [PATCH 28/91] We don't need to be able to disable websocket --- moulinette/__init__.py | 6 ++--- moulinette/interfaces/api.py | 47 ++++++++++++++---------------------- test/conftest.py | 4 +-- 3 files changed, 22 insertions(+), 35 deletions(-) diff --git a/moulinette/__init__.py b/moulinette/__init__.py index d9df19b1..7c91a579 100755 --- a/moulinette/__init__.py +++ b/moulinette/__init__.py @@ -73,7 +73,7 @@ def init(logging_config=None, **kwargs): # Easy access to interfaces def api( - namespaces, host="localhost", port=80, routes={}, use_websocket=True, use_cache=True + namespaces, host="localhost", port=80, routes={}, use_cache=True ): """Web server (API) interface @@ -85,7 +85,6 @@ def api( - port -- Server port to bind to - routes -- A dict of additional routes to add in the form of {(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 @@ -97,8 +96,7 @@ def api( namespaces=namespaces, use_cache=use_cache) interface = Interface(actionsmap=actionsmap, - routes=routes, - use_websocket=use_websocket) + routes=routes) interface.run(host, port) except MoulinetteError as e: import logging diff --git a/moulinette/interfaces/api.py b/moulinette/interfaces/api.py index ee3757bc..084e5671 100644 --- a/moulinette/interfaces/api.py +++ b/moulinette/interfaces/api.py @@ -10,7 +10,7 @@ from gevent import sleep from gevent.queue import Queue from geventwebsocket import WebSocketError -from bottle import run, request, response, Bottle, HTTPResponse +from bottle import request, response, Bottle, HTTPResponse from bottle import abort from moulinette import msignals, m18n, env @@ -219,22 +219,18 @@ class _ActionsMapPlugin(object): Keyword arguments: - 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" api = 2 - def __init__(self, actionsmap, use_websocket, log_queues={}): + def __init__(self, actionsmap, log_queues={}): # Connect signals to handlers 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.use_websocket = use_websocket self.log_queues = log_queues # TODO: Save and load secrets? self.secrets = {} @@ -290,13 +286,12 @@ class _ActionsMapPlugin(object): ) # Append messages route - if self.use_websocket: - app.route( - "/messages", - name="messages", - callback=self.messages, - skip=["actionsmap"], - ) + app.route( + "/messages", + name="messages", + callback=self.messages, + skip=["actionsmap"], + ) # Append routes from the actions map for (m, p) in self.actionsmap.parser.routes: @@ -737,14 +732,12 @@ class Interface(BaseInterface): - actionsmap -- The ActionsMap instance to connect to - routes -- A dict of additional routes to add in the form of {(method, path): callback} - - use_websocket -- Serve via WSGI to handle asynchronous responses - log_queues -- A LogQueues object or None to retrieve it from registered logging handlers """ - def __init__(self, actionsmap, routes={}, use_websocket=True, log_queues=None): - self.use_websocket = use_websocket + def __init__(self, actionsmap, routes={}, log_queues=None): # Attempt to retrieve log queues from an APIQueueHandler if log_queues is None: @@ -776,7 +769,7 @@ class Interface(BaseInterface): app.install(filter_csrf) app.install(apiheader) app.install(api18n) - app.install(_ActionsMapPlugin(actionsmap, use_websocket, log_queues)) + app.install(_ActionsMapPlugin(actionsmap, log_queues)) # Append default routes # app.route(['/api', '/api/'], method='GET', @@ -801,23 +794,19 @@ class Interface(BaseInterface): """ logger.debug( - "starting the server instance in %s:%d with websocket=%s", + "starting the server instance in %s:%d", host, port, - self.use_websocket, ) try: - if self.use_websocket: - from gevent.pywsgi import WSGIServer - from geventwebsocket.handler import WebSocketHandler + from gevent.pywsgi import WSGIServer + from geventwebsocket.handler import WebSocketHandler - server = WSGIServer( - (host, port), self._app, handler_class=WebSocketHandler - ) - server.serve_forever() - else: - run(self._app, host=host, port=port) + server = WSGIServer( + (host, port), self._app, handler_class=WebSocketHandler + ) + server.serve_forever() except IOError as e: logger.exception("unable to start the server instance on %s:%d", host, port) if e.args[0] == errno.EADDRINUSE: diff --git a/test/conftest.py b/test/conftest.py index 6df66806..59c7f832 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -125,7 +125,7 @@ def moulinette_webapi(moulinette): CookiePolicy.return_ok_secure = return_true - moulinette_webapi = moulinette.core.init_interface( + moulinette_webapi = moulinette.init_interface( "api", kwargs={"routes": {}, "use_websocket": False}, actionsmap={"namespaces": ["moulitest"], "use_cache": True}, @@ -148,7 +148,7 @@ def moulinette_cli(moulinette, mocker): help="Log and print debug messages", ) mocker.patch("os.isatty", return_value=True) - moulinette_cli = moulinette.core.init_interface( + moulinette_cli = moulinette.init_interface( "cli", actionsmap={ "namespaces": ["moulitest"], From fce96ad48f43f4603752659acaaabf4016e0a14f Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 1 May 2020 01:37:02 +0200 Subject: [PATCH 29/91] We don't need to be able to not use the cache... --- moulinette/__init__.py | 11 ++--------- moulinette/actionsmap.py | 26 ++++++++++---------------- 2 files changed, 12 insertions(+), 25 deletions(-) diff --git a/moulinette/__init__.py b/moulinette/__init__.py index 7c91a579..141ac60f 100755 --- a/moulinette/__init__.py +++ b/moulinette/__init__.py @@ -73,7 +73,7 @@ def init(logging_config=None, **kwargs): # Easy access to interfaces def api( - namespaces, host="localhost", port=80, routes={}, use_cache=True + namespaces, host="localhost", port=80, routes={} ): """Web server (API) interface @@ -85,16 +85,13 @@ def api( - port -- Server port to bind to - routes -- A dict of additional routes to add in the form of {(method, uri): callback} - - use_cache -- False if it should parse the actions map file - instead of using the cached one """ from moulinette.actionsmap import ActionsMap from moulinette.interfaces.api import Interface, ActionsMapParser try: actionsmap = ActionsMap(ActionsMapParser, - namespaces=namespaces, - use_cache=use_cache) + namespaces=namespaces) interface = Interface(actionsmap=actionsmap, routes=routes) interface.run(host, port) @@ -113,7 +110,6 @@ def api( def cli( namespaces, args, - use_cache=True, output_as=None, password=None, timeout=None, @@ -127,8 +123,6 @@ def cli( Keyword arguments: - namespaces -- The list of namespaces to use - 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 moulinette.interfaces.cli.Interface for possible values - password -- The password to use in case of authentication @@ -141,7 +135,6 @@ def cli( try: actionsmap = ActionsMap(ActionsMapParser, namespaces=namespaces, - use_cache=use_cache, parser_kwargs=parser_kwargs) interface = Interface(actionsmap=actionsmap) interface.run(args, output_as=output_as, password=password, timeout=timeout) diff --git a/moulinette/actionsmap.py b/moulinette/actionsmap.py index 11f7e2be..bccfdb9d 100644 --- a/moulinette/actionsmap.py +++ b/moulinette/actionsmap.py @@ -405,18 +405,15 @@ class ActionsMap(object): - parser_class -- The BaseActionsMapParser derived class to use for parsing the actions map - namespaces -- The list of namespaces to use - - use_cache -- False if it should parse the actions map file - instead of using the cached one - parser_kwargs -- A dict of arguments to pass to the parser class at construction """ - def __init__(self, parser_class, namespaces=[], use_cache=True, parser_kwargs={}): + def __init__(self, parser_class, namespaces=[], parser_kwargs={}): if not issubclass(parser_class, BaseActionsMapParser): raise ValueError("Invalid parser class '%s'" % parser_class.__name__) self.parser_class = parser_class - self.use_cache = use_cache moulinette_env = init_moulinette_env() DATA_DIR = moulinette_env["DATA_DIR"] @@ -439,21 +436,19 @@ class ActionsMap(object): actionsmap_yml_stat.st_mtime, ) - if use_cache and os.path.exists(actionsmap_pkl): + if os.path.exists(actionsmap_pkl): + self.from_cache = True try: # Attempt to load cache with open(actionsmap_pkl) as f: actionsmaps[n] = pickle.load(f) # TODO: Switch to python3 and catch proper exception except (IOError, EOFError): - self.use_cache = False + self.from_cache = False actionsmaps = self.generate_cache(namespaces) - elif use_cache: # cached file doesn't exists - self.use_cache = False + else: # cache file doesn't exists + self.from_cache = False actionsmaps = self.generate_cache(namespaces) - elif n not in actionsmaps: - with open(actionsmap_yml) as f: - actionsmaps[n] = ordered_yaml_load(f) # Load translations m18n.load_namespace(n) @@ -668,11 +663,10 @@ class ActionsMap(object): An interface relevant's parser object """ - # Get extra parameters - if self.use_cache: - validate_extra = False - else: - validate_extra = True + + # If loading from cache, extra were already checked when cache was + # loaded ? Not sure about this ... old code is a bit mysterious... + validate_extra = not self.from_cache # Instantiate parser # From a34fb7c66590580dd0e495129f8ac1a9c3b38368 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 1 May 2020 01:44:18 +0200 Subject: [PATCH 30/91] We don't need to be able to auth in cli --- moulinette/__init__.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/moulinette/__init__.py b/moulinette/__init__.py index 141ac60f..42cc7a38 100755 --- a/moulinette/__init__.py +++ b/moulinette/__init__.py @@ -111,7 +111,6 @@ def cli( namespaces, args, output_as=None, - password=None, timeout=None, parser_kwargs={}, ): @@ -125,7 +124,6 @@ def cli( - args -- A list of argument strings - output_as -- Output result in another format, see moulinette.interfaces.cli.Interface for possible values - - password -- The password to use in case of authentication - parser_kwargs -- A dict of arguments to pass to the parser class at construction @@ -137,7 +135,7 @@ def cli( namespaces=namespaces, parser_kwargs=parser_kwargs) interface = Interface(actionsmap=actionsmap) - interface.run(args, output_as=output_as, password=password, timeout=timeout) + interface.run(args, output_as=output_as, timeout=timeout) except MoulinetteError as e: import logging From d2f68cb536541b2b737a8994ace2130a14b38e5c Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 1 May 2020 02:03:09 +0200 Subject: [PATCH 31/91] We don't need to auth in CLI --- moulinette/interfaces/cli.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/moulinette/interfaces/cli.py b/moulinette/interfaces/cli.py index 804028bc..5c5e4f60 100644 --- a/moulinette/interfaces/cli.py +++ b/moulinette/interfaces/cli.py @@ -431,7 +431,7 @@ class Interface(BaseInterface): self.actionsmap = actionsmap - def run(self, args, output_as=None, password=None, timeout=None): + def run(self, args, output_as=None, timeout=None): """Run the moulinette Process the action corresponding to the given arguments 'args' @@ -443,7 +443,6 @@ class Interface(BaseInterface): - json: return a JSON encoded string - plain: return a script-readable output - 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 """ @@ -454,11 +453,7 @@ class Interface(BaseInterface): argcomplete.autocomplete(self.actionsmap.parser._parser) # Set handler for authentication - if password: - msignals.set_handler("authenticate", lambda a: a(password=password)) - else: - if os.isatty(1): - msignals.set_handler("authenticate", self._do_authenticate) + msignals.set_handler("authenticate", self._do_authenticate) try: ret = self.actionsmap.process(args, timeout=timeout) @@ -490,7 +485,11 @@ class Interface(BaseInterface): 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") msg = m18n.n(help) if help else m18n.g("password") return authenticator(password=self._do_prompt(msg, True, False, color="yellow")) From 57d1b2b6dbc6f7c63d4e93b3b80c26187f791f2c Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 1 May 2020 02:10:11 +0200 Subject: [PATCH 32/91] Bit of tidying up --- moulinette/__init__.py | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/moulinette/__init__.py b/moulinette/__init__.py index 42cc7a38..4d7bf14c 100755 --- a/moulinette/__init__.py +++ b/moulinette/__init__.py @@ -72,9 +72,7 @@ def init(logging_config=None, **kwargs): # Easy access to interfaces -def api( - namespaces, host="localhost", port=80, routes={} -): +def api(namespaces, host="localhost", port=80, routes={}): """Web server (API) interface Run a HTTP server with the moulinette for an API usage. @@ -107,13 +105,7 @@ def api( return 0 -def cli( - namespaces, - args, - output_as=None, - timeout=None, - parser_kwargs={}, -): +def cli(namespaces, args, output_as=None, timeout=None, parser_kwargs={}): """Command line interface Execute an action with the moulinette from the CLI and print its From 1849d1aa3b10f26bf567c03685f36ed272dc1f29 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 1 May 2020 03:07:25 +0200 Subject: [PATCH 33/91] Simplify(?) ActionsMap initialization by removing some obscure kwargs handling --- moulinette/__init__.py | 12 +++++------- moulinette/actionsmap.py | 36 ++++++++++-------------------------- 2 files changed, 15 insertions(+), 33 deletions(-) diff --git a/moulinette/__init__.py b/moulinette/__init__.py index 4d7bf14c..0e9188ec 100755 --- a/moulinette/__init__.py +++ b/moulinette/__init__.py @@ -88,7 +88,7 @@ def api(namespaces, host="localhost", port=80, routes={}): from moulinette.actionsmap import ActionsMap from moulinette.interfaces.api import Interface, ActionsMapParser try: - actionsmap = ActionsMap(ActionsMapParser, + actionsmap = ActionsMap(ActionsMapParser(), namespaces=namespaces) interface = Interface(actionsmap=actionsmap, routes=routes) @@ -105,7 +105,7 @@ def api(namespaces, host="localhost", port=80, routes={}): return 0 -def cli(namespaces, args, output_as=None, timeout=None, parser_kwargs={}): +def cli(namespaces, args, top_parser, output_as=None, timeout=None): """Command line interface Execute an action with the moulinette from the CLI and print its @@ -116,16 +116,14 @@ def cli(namespaces, args, output_as=None, timeout=None, parser_kwargs={}): - args -- A list of argument strings - output_as -- Output result in another format, see moulinette.interfaces.cli.Interface for possible values - - parser_kwargs -- A dict of arguments to pass to the parser - class at construction + - top_parser -- The top parser used to build the ActionsMapParser """ from moulinette.actionsmap import ActionsMap from moulinette.interfaces.cli import Interface, ActionsMapParser try: - actionsmap = ActionsMap(ActionsMapParser, - namespaces=namespaces, - parser_kwargs=parser_kwargs) + actionsmap = ActionsMap(ActionsMapParser(top_parser=top_parser), + namespaces=namespaces) interface = Interface(actionsmap=actionsmap) interface.run(args, output_as=output_as, timeout=timeout) except MoulinetteError as e: diff --git a/moulinette/actionsmap.py b/moulinette/actionsmap.py index bccfdb9d..b36e1ece 100644 --- a/moulinette/actionsmap.py +++ b/moulinette/actionsmap.py @@ -402,18 +402,14 @@ class ActionsMap(object): all available namespaces. Keyword arguments: - - parser_class -- The BaseActionsMapParser derived class to use - for parsing the actions map + - top_parser -- A BaseActionsMapParser-derived instance to use for + parsing the actions map - namespaces -- The list of namespaces to use - - parser_kwargs -- A dict of arguments to pass to the parser - class at construction - """ - def __init__(self, parser_class, namespaces=[], parser_kwargs={}): - if not issubclass(parser_class, BaseActionsMapParser): - raise ValueError("Invalid parser class '%s'" % parser_class.__name__) - self.parser_class = parser_class + def __init__(self, top_parser, namespaces=[]): + + assert isinstance(top_parser, BaseActionsMapParser), "Invalid parser class '%s'" % top_parser.__class__.__name__ moulinette_env = init_moulinette_env() DATA_DIR = moulinette_env["DATA_DIR"] @@ -454,13 +450,8 @@ class ActionsMap(object): m18n.load_namespace(n) # Generate parsers - self.extraparser = ExtraArgumentParser(parser_class.interface) - self._parser = self._construct_parser(actionsmaps, **parser_kwargs) - - @property - def parser(self): - """Return the instance of the interface's actions map parser""" - return self._parser + self.extraparser = ExtraArgumentParser(top_parser.interface) + self.parser = self._construct_parser(actionsmaps, top_parser) def get_authenticator_for_profile(self, auth_profile): @@ -649,15 +640,15 @@ class ActionsMap(object): # Private methods - def _construct_parser(self, actionsmaps, **kwargs): + def _construct_parser(self, actionsmaps, top_parser): """ Construct the parser with the actions map Keyword arguments: - actionsmaps -- A dict of multi-level dictionnary of categories/actions/arguments list for each namespaces - - **kwargs -- Additionnal arguments to pass at the parser - class instantiation + - top_parser -- A BaseActionsMapParser-derived instance to use for + parsing the actions map Returns: An interface relevant's parser object @@ -668,13 +659,6 @@ class ActionsMap(object): # loaded ? Not sure about this ... old code is a bit mysterious... validate_extra = not self.from_cache - # Instantiate parser - # - # this either returns: - # * moulinette.interfaces.cli.ActionsMapParser - # * moulinette.interfaces.api.ActionsMapParser - top_parser = self.parser_class(**kwargs) - # namespace, actionsmap is a tuple where: # # * namespace define the top "name", for us it will always be From c750226a3b0516547ac9e8f4a34fab020ce402ad Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 1 May 2020 03:48:01 +0200 Subject: [PATCH 34/91] We don't need no namespaces ... but let's it customizable through a var env if needed... --- moulinette/__init__.py | 23 +++++-------- moulinette/actionsmap.py | 72 +++++++++++++++++++--------------------- moulinette/globals.py | 1 + 3 files changed, 43 insertions(+), 53 deletions(-) diff --git a/moulinette/__init__.py b/moulinette/__init__.py index 0e9188ec..d9badd4c 100755 --- a/moulinette/__init__.py +++ b/moulinette/__init__.py @@ -72,13 +72,12 @@ def init(logging_config=None, **kwargs): # Easy access to interfaces -def api(namespaces, host="localhost", port=80, routes={}): +def api(host="localhost", port=80, routes={}): """Web server (API) interface Run a HTTP server with the moulinette for an API usage. Keyword arguments: - - namespaces -- The list of namespaces to use - host -- Server address to bind to - port -- Server port to bind to - routes -- A dict of additional routes to add in the form of @@ -88,31 +87,27 @@ def api(namespaces, host="localhost", port=80, routes={}): from moulinette.actionsmap import ActionsMap from moulinette.interfaces.api import Interface, ActionsMapParser try: - actionsmap = ActionsMap(ActionsMapParser(), - namespaces=namespaces) + actionsmap = ActionsMap(ActionsMapParser()) interface = Interface(actionsmap=actionsmap, routes=routes) interface.run(host, port) except MoulinetteError as e: import logging - - logging.getLogger(namespaces[0]).error(e.strerror) - return e.errno if hasattr(e, "errno") else 1 + logging.getLogger().error(e.strerror) + return 1 except KeyboardInterrupt: import logging - - logging.getLogger(namespaces[0]).info(m18n.g("operation_interrupted")) + logging.getLogger().info(m18n.g("operation_interrupted")) return 0 -def cli(namespaces, args, top_parser, output_as=None, timeout=None): +def cli(args, top_parser, output_as=None, timeout=None): """Command line interface Execute an action with the moulinette from the CLI and print its result in a readable format. Keyword arguments: - - namespaces -- The list of namespaces to use - args -- A list of argument strings - output_as -- Output result in another format, see moulinette.interfaces.cli.Interface for possible values @@ -122,14 +117,12 @@ def cli(namespaces, args, top_parser, output_as=None, timeout=None): from moulinette.actionsmap import ActionsMap from moulinette.interfaces.cli import Interface, ActionsMapParser try: - actionsmap = ActionsMap(ActionsMapParser(top_parser=top_parser), - namespaces=namespaces) + actionsmap = ActionsMap(ActionsMapParser(top_parser=top_parser)) interface = Interface(actionsmap=actionsmap) interface.run(args, output_as=output_as, timeout=timeout) except MoulinetteError as e: import logging - - logging.getLogger(namespaces[0]).error(e.strerror) + logging.getLogger().error(e.strerror) return 1 return 0 diff --git a/moulinette/actionsmap.py b/moulinette/actionsmap.py index b36e1ece..221a46fb 100644 --- a/moulinette/actionsmap.py +++ b/moulinette/actionsmap.py @@ -4,6 +4,7 @@ import os import re import logging import yaml +import glob import cPickle as pickle from time import time from collections import OrderedDict @@ -398,16 +399,14 @@ class ActionsMap(object): Moreover, the action can have specific argument(s). This class allows to manipulate one or several actions maps - associated to a namespace. If no namespace is given, it will load - all available namespaces. + associated to a namespace. Keyword arguments: - top_parser -- A BaseActionsMapParser-derived instance to use for parsing the actions map - - namespaces -- The list of namespaces to use """ - def __init__(self, top_parser, namespaces=[]): + def __init__(self, top_parser): assert isinstance(top_parser, BaseActionsMapParser), "Invalid parser class '%s'" % top_parser.__class__.__name__ @@ -415,12 +414,10 @@ class ActionsMap(object): DATA_DIR = moulinette_env["DATA_DIR"] CACHE_DIR = moulinette_env["CACHE_DIR"] - if len(namespaces) == 0: - namespaces = self.get_namespaces() actionsmaps = OrderedDict() # Iterate over actions map namespaces - for n in namespaces: + for n in self.get_namespaces(): logger.debug("loading actions map namespace '%s'", n) actionsmap_yml = "%s/actionsmap/%s.yml" % (DATA_DIR, n) @@ -441,10 +438,10 @@ class ActionsMap(object): # TODO: Switch to python3 and catch proper exception except (IOError, EOFError): self.from_cache = False - actionsmaps = self.generate_cache(namespaces) + actionsmaps[n] = self.generate_cache(n) else: # cache file doesn't exists self.from_cache = False - actionsmaps = self.generate_cache(namespaces) + actionsmaps[n] = self.generate_cache(n) # Load translations m18n.load_namespace(n) @@ -587,56 +584,55 @@ class ActionsMap(object): moulinette_env = init_moulinette_env() DATA_DIR = moulinette_env["DATA_DIR"] - for f in os.listdir("%s/actionsmap" % DATA_DIR): - if f.endswith(".yml"): - namespaces.append(f[:-4]) + # This var is ['*'] by default but could be set for example to + # ['yunohost', 'yml_*'] + 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 @classmethod - def generate_cache(klass, namespaces=None): + def generate_cache(klass, namespace): """ Generate cache for the actions map's file(s) Keyword arguments: - - namespaces -- A list of namespaces to generate cache for + - namespace -- The namespace to generate cache for Returns: - A dict of actions map for each namespaces - + The action map for the namespace """ moulinette_env = init_moulinette_env() CACHE_DIR = moulinette_env["CACHE_DIR"] DATA_DIR = moulinette_env["DATA_DIR"] - actionsmaps = {} - if not namespaces: - namespaces = klass.get_namespaces() - # Iterate over actions map namespaces - for n in namespaces: - logger.debug("generating cache for actions map namespace '%s'", n) + logger.debug("generating cache for actions map namespace '%s'", namespace) - # Read actions map from yaml file - am_file = "%s/actionsmap/%s.yml" % (DATA_DIR, n) - with open(am_file, "r") as f: - actionsmaps[n] = ordered_yaml_load(f) + # Read actions map from yaml file + am_file = "%s/actionsmap/%s.yml" % (DATA_DIR, namespace) + with open(am_file, "r") as f: + actionsmap = ordered_yaml_load(f) - # at installation, cachedir might not exists - if os.path.exists("%s/actionsmap/" % CACHE_DIR): - # clean old cached files - for i in os.listdir("%s/actionsmap/" % CACHE_DIR): - if i.endswith(".pkl"): - os.remove("%s/actionsmap/%s" % (CACHE_DIR, i)) + # at installation, cachedir might not exists + for old_cache in glob.glob("%s/actionsmap/%s-*.pkl" % (CACHE_DIR, namespace)): + os.remove(old_cache) - # Cache actions map into pickle file - am_file_stat = os.stat(am_file) + # Cache actions map into pickle 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, "w", subdir="actionsmap") as f: - pickle.dump(actionsmaps[n], f) + with open_cachefile(pkl, "w", subdir="actionsmap") as f: + pickle.dump(actionsmap, f) - return actionsmaps + return actionsmap # Private methods diff --git a/moulinette/globals.py b/moulinette/globals.py index 39f45d93..025aab52 100644 --- a/moulinette/globals.py +++ b/moulinette/globals.py @@ -11,4 +11,5 @@ def init_moulinette_env(): "MOULINETTE_LOCALES_DIR", "/usr/share/moulinette/locale" ), "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 } From 559f40a4eaf8aad996fd295ad48a2008828c5843 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 1 May 2020 03:58:37 +0200 Subject: [PATCH 35/91] Another round of simplification for interface init... --- moulinette/__init__.py | 15 ++++----------- moulinette/interfaces/api.py | 6 ++++-- moulinette/interfaces/cli.py | 6 ++++-- 3 files changed, 12 insertions(+), 15 deletions(-) diff --git a/moulinette/__init__.py b/moulinette/__init__.py index d9badd4c..3a80381d 100755 --- a/moulinette/__init__.py +++ b/moulinette/__init__.py @@ -84,13 +84,9 @@ def api(host="localhost", port=80, routes={}): {(method, uri): callback} """ - from moulinette.actionsmap import ActionsMap - from moulinette.interfaces.api import Interface, ActionsMapParser + from moulinette.interfaces.api import Interface as Api try: - actionsmap = ActionsMap(ActionsMapParser()) - interface = Interface(actionsmap=actionsmap, - routes=routes) - interface.run(host, port) + Api(routes=routes).run(host, port) except MoulinetteError as e: import logging logging.getLogger().error(e.strerror) @@ -114,12 +110,9 @@ def cli(args, top_parser, output_as=None, timeout=None): - top_parser -- The top parser used to build the ActionsMapParser """ - from moulinette.actionsmap import ActionsMap - from moulinette.interfaces.cli import Interface, ActionsMapParser + from moulinette.interfaces.cli import Interface as Cli try: - actionsmap = ActionsMap(ActionsMapParser(top_parser=top_parser)) - interface = Interface(actionsmap=actionsmap) - interface.run(args, output_as=output_as, timeout=timeout) + Cli(top_parser=top_parser).run(args, output_as=output_as, timeout=timeout) except MoulinetteError as e: import logging logging.getLogger().error(e.strerror) diff --git a/moulinette/interfaces/api.py b/moulinette/interfaces/api.py index 084e5671..f251c215 100644 --- a/moulinette/interfaces/api.py +++ b/moulinette/interfaces/api.py @@ -14,6 +14,7 @@ from bottle import request, response, Bottle, HTTPResponse from bottle import abort from moulinette import msignals, m18n, env +from moulinette.actionsmap import ActionsMap from moulinette.core import MoulinetteError from moulinette.interfaces import ( BaseActionsMapParser, @@ -729,7 +730,6 @@ class Interface(BaseInterface): actions map. Keyword arguments: - - actionsmap -- The ActionsMap instance to connect to - routes -- A dict of additional routes to add in the form of {(method, path): callback} - log_queues -- A LogQueues object or None to retrieve it from @@ -737,7 +737,9 @@ class Interface(BaseInterface): """ - def __init__(self, actionsmap, routes={}, log_queues=None): + def __init__(self, routes={}, log_queues=None): + + actionsmap = ActionsMap(ActionsMapParser()) # Attempt to retrieve log queues from an APIQueueHandler if log_queues is None: diff --git a/moulinette/interfaces/cli.py b/moulinette/interfaces/cli.py index 5c5e4f60..f1e8f834 100644 --- a/moulinette/interfaces/cli.py +++ b/moulinette/interfaces/cli.py @@ -12,6 +12,7 @@ from datetime import date, datetime import argcomplete from moulinette import msignals, m18n +from moulinette.actionsmap import ActionsMap from moulinette.core import MoulinetteError from moulinette.interfaces import ( BaseActionsMapParser, @@ -419,7 +420,8 @@ class Interface(BaseInterface): """ - def __init__(self, actionsmap): + def __init__(self, top_parser=None): + # Set user locale m18n.set_locale(get_locale()) @@ -429,7 +431,7 @@ class Interface(BaseInterface): msignals.set_handler("authenticate", self._do_authenticate) msignals.set_handler("prompt", self._do_prompt) - self.actionsmap = actionsmap + self.actionsmap = ActionsMap(ActionsMapParser(top_parser=top_parser)) def run(self, args, output_as=None, timeout=None): """Run the moulinette From 89ad543797c1c7e3f7eca3a51df22f8582a72865 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 1 May 2020 04:33:14 +0200 Subject: [PATCH 36/91] Tweaking debug messages --- moulinette/actionsmap.py | 3 ++- moulinette/interfaces/__init__.py | 5 ----- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/moulinette/actionsmap.py b/moulinette/actionsmap.py index 221a46fb..49f46565 100644 --- a/moulinette/actionsmap.py +++ b/moulinette/actionsmap.py @@ -283,7 +283,6 @@ class ExtraArgumentParser(object): if iface in klass.skipped_iface: continue self.extra[klass.name] = klass - logger.debug("extra parameter classes loaded: %s", self.extra.keys()) def validate(self, arg_name, parameters): """ @@ -651,6 +650,8 @@ class ActionsMap(object): """ + logger.debug("building parser...") + # If loading from cache, extra were already checked when cache was # loaded ? Not sure about this ... old code is a bit mysterious... validate_extra = not self.from_cache diff --git a/moulinette/interfaces/__init__.py b/moulinette/interfaces/__init__.py index 4f2e33dd..efc21db9 100644 --- a/moulinette/interfaces/__init__.py +++ b/moulinette/interfaces/__init__.py @@ -342,11 +342,6 @@ class _CallbackAction(argparse.Action): self.callback_method = callback.get("method") self.callback_kwargs = callback.get("kwargs", {}) self.callback_return = callback.get("return", False) - logger.debug( - "registering new callback action '{0}' to {1}".format( - self.callback_method, option_strings - ) - ) @property def callback(self): From 677f4518e4c790510f08989be0f5500bd3f43a2d Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 1 May 2020 05:43:32 +0200 Subject: [PATCH 37/91] Ugly hack to only load 1 category to speed up execution time --- moulinette/__init__.py | 3 ++- moulinette/actionsmap.py | 15 ++++++++++++++- moulinette/interfaces/cli.py | 4 ++-- 3 files changed, 18 insertions(+), 4 deletions(-) diff --git a/moulinette/__init__.py b/moulinette/__init__.py index 3a80381d..d27acac6 100755 --- a/moulinette/__init__.py +++ b/moulinette/__init__.py @@ -112,7 +112,8 @@ def cli(args, top_parser, output_as=None, timeout=None): """ from moulinette.interfaces.cli import Interface as Cli try: - Cli(top_parser=top_parser).run(args, output_as=output_as, timeout=timeout) + load_only_category = args[0] if args and not args[0].startswith("-") else None + Cli(top_parser=top_parser, load_only_category=load_only_category).run(args, output_as=output_as, timeout=timeout) except MoulinetteError as e: import logging logging.getLogger().error(e.strerror) diff --git a/moulinette/actionsmap.py b/moulinette/actionsmap.py index 49f46565..4329160e 100644 --- a/moulinette/actionsmap.py +++ b/moulinette/actionsmap.py @@ -403,9 +403,13 @@ class ActionsMap(object): Keyword arguments: - top_parser -- A BaseActionsMapParser-derived instance to use for parsing the actions map + - load_only_category -- A name of a category that should only be the + one loaded because it's been already determined + that's the only one relevant ... used for optimization + purposes... """ - def __init__(self, top_parser): + def __init__(self, top_parser, load_only_category=None): assert isinstance(top_parser, BaseActionsMapParser), "Invalid parser class '%s'" % top_parser.__class__.__name__ @@ -442,6 +446,13 @@ class ActionsMap(object): self.from_cache = False actionsmaps[n] = self.generate_cache(n) + # If load_only_category is set, and *if* the target category + # is in the actionsmap, we'll load only that one. + # If we filter it even if it doesn't exist, we'll end up with a + # 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 m18n.load_namespace(n) @@ -651,6 +662,7 @@ class ActionsMap(object): """ logger.debug("building parser...") + start = time() # If loading from cache, extra were already checked when cache was # loaded ? Not sure about this ... old code is a bit mysterious... @@ -756,4 +768,5 @@ class ActionsMap(object): tid, action_options["configuration"] ) + logger.debug("building parser took %.3fs", time() - start) return top_parser diff --git a/moulinette/interfaces/cli.py b/moulinette/interfaces/cli.py index f1e8f834..186c0c89 100644 --- a/moulinette/interfaces/cli.py +++ b/moulinette/interfaces/cli.py @@ -420,7 +420,7 @@ class Interface(BaseInterface): """ - def __init__(self, top_parser=None): + def __init__(self, top_parser=None, load_only_category=None): # Set user locale m18n.set_locale(get_locale()) @@ -431,7 +431,7 @@ class Interface(BaseInterface): msignals.set_handler("authenticate", self._do_authenticate) msignals.set_handler("prompt", self._do_prompt) - self.actionsmap = ActionsMap(ActionsMapParser(top_parser=top_parser)) + self.actionsmap = ActionsMap(ActionsMapParser(top_parser=top_parser), load_only_category=load_only_category) def run(self, args, output_as=None, timeout=None): """Run the moulinette From a2a6e6fe7c1fc370e3fb4aa79afee67345860a50 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 1 May 2020 06:11:14 +0200 Subject: [PATCH 38/91] Attempt to fix the tests :s --- test/conftest.py | 21 +++++---------------- test/test_actionsmap.py | 12 ++++-------- 2 files changed, 9 insertions(+), 24 deletions(-) diff --git a/test/conftest.py b/test/conftest.py index 59c7f832..3f400b17 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -125,13 +125,8 @@ def moulinette_webapi(moulinette): CookiePolicy.return_ok_secure = return_true - moulinette_webapi = moulinette.init_interface( - "api", - kwargs={"routes": {}, "use_websocket": False}, - actionsmap={"namespaces": ["moulitest"], "use_cache": True}, - ) - - return TestApp(moulinette_webapi._app) + from moulinette.interfaces.api import Interface as Api + return TestApp(Api(routes={})._app) @pytest.fixture @@ -148,17 +143,11 @@ def moulinette_cli(moulinette, mocker): help="Log and print debug messages", ) mocker.patch("os.isatty", return_value=True) - moulinette_cli = moulinette.init_interface( - "cli", - actionsmap={ - "namespaces": ["moulitest"], - "use_cache": False, - "parser_kwargs": {"top_parser": parser}, - }, - ) + from moulinette.interfaces.cli import Interface as Cli + cli = Cli(top_parser=parser) mocker.stopall() - return moulinette_cli + return cli @pytest.fixture diff --git a/test/test_actionsmap.py b/test/test_actionsmap.py index b69b5179..de2942fe 100644 --- a/test/test_actionsmap.py +++ b/test/test_actionsmap.py @@ -225,7 +225,7 @@ def test_extra_argument_parser_parse_args(iface, mocker): def test_actions_map_api(): from moulinette.interfaces.api import ActionsMapParser - amap = ActionsMap(ActionsMapParser, use_cache=False) + amap = ActionsMap(ActionsMapParser()) assert amap.parser.global_conf["authenticate"] == "all" assert "default" in amap.parser.global_conf["authenticator"] @@ -235,7 +235,7 @@ def test_actions_map_api(): amap.generate_cache() - amap = ActionsMap(ActionsMapParser, use_cache=True) + amap = ActionsMap(ActionsMapParser()) assert amap.parser.global_conf["authenticate"] == "all" assert "default" in amap.parser.global_conf["authenticator"] @@ -274,9 +274,7 @@ def test_actions_map_cli(): default=False, help="Log and print debug messages", ) - amap = ActionsMap( - ActionsMapParser, use_cache=False, parser_kwargs={"top_parser": parser} - ) + amap = ActionsMap(ActionsMapParser(top_parser=parser)) assert amap.parser.global_conf["authenticate"] == "all" assert "default" in amap.parser.global_conf["authenticator"] @@ -295,9 +293,7 @@ def test_actions_map_cli(): amap.generate_cache() - amap = ActionsMap( - ActionsMapParser, use_cache=True, parser_kwargs={"top_parser": parser} - ) + amap = ActionsMap(ActionsMapParser(top_parser=parser)) assert amap.parser.global_conf["authenticate"] == "all" assert "default" in amap.parser.global_conf["authenticator"] From e8309384e565cb0bb6081d3621b79b5bb94c5e74 Mon Sep 17 00:00:00 2001 From: Kay0u Date: Fri, 1 May 2020 14:00:10 +0200 Subject: [PATCH 39/91] fix tests --- moulinette/actionsmap.py | 6 +++--- test/test_actionsmap.py | 10 +++++----- test/test_auth.py | 14 +++++--------- 3 files changed, 13 insertions(+), 17 deletions(-) diff --git a/moulinette/actionsmap.py b/moulinette/actionsmap.py index 4329160e..2fe17d71 100644 --- a/moulinette/actionsmap.py +++ b/moulinette/actionsmap.py @@ -419,6 +419,7 @@ class ActionsMap(object): actionsmaps = OrderedDict() + self.from_cache = False # Iterate over actions map namespaces for n in self.get_namespaces(): logger.debug("loading actions map namespace '%s'", n) @@ -433,17 +434,16 @@ class ActionsMap(object): ) if os.path.exists(actionsmap_pkl): - self.from_cache = True try: # Attempt to load cache with open(actionsmap_pkl) as f: actionsmaps[n] = pickle.load(f) + + self.from_cache = True # TODO: Switch to python3 and catch proper exception except (IOError, EOFError): - self.from_cache = False actionsmaps[n] = self.generate_cache(n) else: # cache file doesn't exists - self.from_cache = False actionsmaps[n] = self.generate_cache(n) # If load_only_category is set, and *if* the target category diff --git a/test/test_actionsmap.py b/test/test_actionsmap.py index de2942fe..69af0472 100644 --- a/test/test_actionsmap.py +++ b/test/test_actionsmap.py @@ -158,10 +158,10 @@ def test_required_paremeter_missing_value(iface, caplog): def test_actions_map_unknown_authenticator(monkeypatch, 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() - amap = ActionsMap(BaseActionsMapParser) + amap = ActionsMap(BaseActionsMapParser()) with pytest.raises(ValueError) as exception: amap.get_authenticator_for_profile("unknown") assert "Unknown authenticator" in str(exception) @@ -233,7 +233,7 @@ def test_actions_map_api(): assert ("GET", "/test-auth/default") in amap.parser.routes assert ("POST", "/test-auth/subcat/post") in amap.parser.routes - amap.generate_cache() + amap.generate_cache("moulitest") amap = ActionsMap(ActionsMapParser()) @@ -247,7 +247,7 @@ def test_actions_map_api(): def test_actions_map_import_error(mocker): from moulinette.interfaces.api import ActionsMapParser - amap = ActionsMap(ActionsMapParser) + amap = ActionsMap(ActionsMapParser()) from moulinette.core import MoulinetteLock @@ -291,7 +291,7 @@ def test_actions_map_cli(): .choices ) - amap.generate_cache() + amap.generate_cache("moulitest") amap = ActionsMap(ActionsMapParser(top_parser=parser)) diff --git a/test/test_auth.py b/test/test_auth.py index dd95d9c7..a7a79c90 100644 --- a/test/test_auth.py +++ b/test/test_auth.py @@ -216,18 +216,15 @@ class TestAuthCLI: assert "some_data_from_default" in message.out - moulinette_cli.run( - ["testauth", "default"], output_as="plain", password="default" - ) + moulinette_cli.run(["testauth", "default"], output_as="plain") message = capsys.readouterr() assert "some_data_from_default" in message.out def test_login_bad_password(self, moulinette_cli, capsys, mocker): + mocker.patch("getpass.getpass", return_value="Bad Password") with pytest.raises(MoulinetteError): - moulinette_cli.run( - ["testauth", "default"], output_as="plain", password="Bad Password" - ) + moulinette_cli.run(["testauth", "default"], output_as="plain") mocker.patch("getpass.getpass", return_value="Bad Password") with pytest.raises(MoulinetteError): @@ -242,10 +239,9 @@ class TestAuthCLI: expected_msg = translation.format() assert expected_msg in str(exception) + mocker.patch("getpass.getpass", return_value="yoloswag") with pytest.raises(MoulinetteError) as exception: - moulinette_cli.run( - ["testauth", "default"], output_as="none", password="yoloswag" - ) + moulinette_cli.run(["testauth", "default"], output_as="none") expected_msg = translation.format() assert expected_msg in str(exception) From 708b0330d2c4a0acf0343f7c3ccbd400609b9810 Mon Sep 17 00:00:00 2001 From: Kay0u Date: Fri, 1 May 2020 14:03:23 +0200 Subject: [PATCH 40/91] black/flake8 --- moulinette/__init__.py | 9 ++++++++- moulinette/actionsmap.py | 14 +++++++++++--- moulinette/globals.py | 4 +++- moulinette/interfaces/api.py | 13 +++---------- moulinette/interfaces/cli.py | 5 ++++- test/conftest.py | 2 ++ 6 files changed, 31 insertions(+), 16 deletions(-) diff --git a/moulinette/__init__.py b/moulinette/__init__.py index d27acac6..cf1992b8 100755 --- a/moulinette/__init__.py +++ b/moulinette/__init__.py @@ -85,14 +85,17 @@ def api(host="localhost", port=80, routes={}): """ from moulinette.interfaces.api import Interface as Api + try: Api(routes=routes).run(host, port) except MoulinetteError as e: import logging + logging.getLogger().error(e.strerror) return 1 except KeyboardInterrupt: import logging + logging.getLogger().info(m18n.g("operation_interrupted")) return 0 @@ -111,11 +114,15 @@ def cli(args, top_parser, output_as=None, timeout=None): """ from moulinette.interfaces.cli import Interface as Cli + try: load_only_category = args[0] if args and not args[0].startswith("-") else None - Cli(top_parser=top_parser, load_only_category=load_only_category).run(args, output_as=output_as, timeout=timeout) + Cli(top_parser=top_parser, load_only_category=load_only_category).run( + args, output_as=output_as, timeout=timeout + ) except MoulinetteError as e: import logging + logging.getLogger().error(e.strerror) return 1 return 0 diff --git a/moulinette/actionsmap.py b/moulinette/actionsmap.py index 2fe17d71..7a365469 100644 --- a/moulinette/actionsmap.py +++ b/moulinette/actionsmap.py @@ -411,7 +411,9 @@ class ActionsMap(object): def __init__(self, top_parser, load_only_category=None): - assert isinstance(top_parser, BaseActionsMapParser), "Invalid parser class '%s'" % top_parser.__class__.__name__ + assert isinstance(top_parser, BaseActionsMapParser), ( + "Invalid parser class '%s'" % top_parser.__class__.__name__ + ) moulinette_env = init_moulinette_env() DATA_DIR = moulinette_env["DATA_DIR"] @@ -451,7 +453,11 @@ class ActionsMap(object): # If we filter it even if it doesn't exist, we'll end up with a # 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"]} + actionsmaps[n] = { + k: v + for k, v in actionsmaps[n].items() + if k in [load_only_category, "_global"] + } # Load translations m18n.load_namespace(n) @@ -600,7 +606,9 @@ class ActionsMap(object): # 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))) + 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] diff --git a/moulinette/globals.py b/moulinette/globals.py index 025aab52..8a169cea 100644 --- a/moulinette/globals.py +++ b/moulinette/globals.py @@ -11,5 +11,7 @@ def init_moulinette_env(): "MOULINETTE_LOCALES_DIR", "/usr/share/moulinette/locale" ), "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 + "NAMESPACES": environ.get( + "MOULINETTE_NAMESPACES", "*" + ).split(), # By default we'll load every namespace we find } diff --git a/moulinette/interfaces/api.py b/moulinette/interfaces/api.py index f251c215..4714dd09 100644 --- a/moulinette/interfaces/api.py +++ b/moulinette/interfaces/api.py @@ -288,10 +288,7 @@ class _ActionsMapPlugin(object): # Append messages 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 @@ -796,18 +793,14 @@ class Interface(BaseInterface): """ logger.debug( - "starting the server instance in %s:%d", - host, - port, + "starting the server instance in %s:%d", host, port, ) try: from gevent.pywsgi import WSGIServer from geventwebsocket.handler import WebSocketHandler - server = WSGIServer( - (host, port), self._app, handler_class=WebSocketHandler - ) + server = WSGIServer((host, port), self._app, handler_class=WebSocketHandler) server.serve_forever() except IOError as e: logger.exception("unable to start the server instance on %s:%d", host, port) diff --git a/moulinette/interfaces/cli.py b/moulinette/interfaces/cli.py index 186c0c89..d8e2dd2b 100644 --- a/moulinette/interfaces/cli.py +++ b/moulinette/interfaces/cli.py @@ -431,7 +431,10 @@ class Interface(BaseInterface): msignals.set_handler("authenticate", self._do_authenticate) msignals.set_handler("prompt", self._do_prompt) - self.actionsmap = ActionsMap(ActionsMapParser(top_parser=top_parser), load_only_category=load_only_category) + self.actionsmap = ActionsMap( + ActionsMapParser(top_parser=top_parser), + load_only_category=load_only_category, + ) def run(self, args, output_as=None, timeout=None): """Run the moulinette diff --git a/test/conftest.py b/test/conftest.py index 3f400b17..8e0ec8ac 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -126,6 +126,7 @@ def moulinette_webapi(moulinette): CookiePolicy.return_ok_secure = return_true from moulinette.interfaces.api import Interface as Api + return TestApp(Api(routes={})._app) @@ -144,6 +145,7 @@ def moulinette_cli(moulinette, mocker): ) mocker.patch("os.isatty", return_value=True) from moulinette.interfaces.cli import Interface as Cli + cli = Cli(top_parser=parser) mocker.stopall() From b97ac05aad0cc3f5db024f47958bae67d7476f51 Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Mon, 4 May 2020 14:00:03 +0200 Subject: [PATCH 41/91] [fix] try to autorestart ldap when the server is down --- locales/en.json | 3 ++- moulinette/authenticators/ldap.py | 23 +++++++++++++++++++---- moulinette/core.py | 4 ++++ 3 files changed, 25 insertions(+), 5 deletions(-) diff --git a/locales/en.json b/locales/en.json index 97ff034f..51e4eb58 100644 --- a/locales/en.json +++ b/locales/en.json @@ -54,5 +54,6 @@ "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_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..." } diff --git a/moulinette/authenticators/ldap.py b/moulinette/authenticators/ldap.py index 2683772d..75554a8c 100644 --- a/moulinette/authenticators/ldap.py +++ b/moulinette/authenticators/ldap.py @@ -2,15 +2,18 @@ # TODO: Use Python3 to remove this fix! from __future__ import absolute_import +import os import logging import random import string import crypt import ldap import ldap.sasl +import time 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 logger = logging.getLogger("moulinette.authenticator.ldap") @@ -69,7 +72,7 @@ class Authenticator(BaseAuthenticator): # Implement virtual methods def authenticate(self, password=None): - try: + def _reconnect(): con = ldap.ldapobject.ReconnectLDAPObject( self._get_uri(), retry_max=10, retry_delay=0.5 ) @@ -80,11 +83,23 @@ class Authenticator(BaseAuthenticator): con.simple_bind_s(self.userdn, password) else: con.simple_bind_s() + + return con + + try: + con = _reconnect() except ldap.INVALID_CREDENTIALS: raise MoulinetteError("invalid_password") except ldap.SERVER_DOWN: - logger.exception("unable to reach the server to authenticate") - raise MoulinetteError("ldap_server_down") + # ldap is down, attempt to restart it before really failing + logger.warning(m18n.g("ldap_server_down")) + 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 try: diff --git a/moulinette/core.py b/moulinette/core.py index d3a2299c..501890ce 100644 --- a/moulinette/core.py +++ b/moulinette/core.py @@ -438,6 +438,10 @@ class MoulinetteError(Exception): self.strerror = msg +class MoulinetteLdapIsDownError(MoulinetteError): + """Used when ldap is down""" + + class MoulinetteLock(object): """Locker for a moulinette instance From 840f27d2fb740c3dd6460070cfe3600aa3a34fd6 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 7 May 2020 00:34:13 +0200 Subject: [PATCH 42/91] Print the goddamn traceback --- moulinette/actionsmap.py | 2 ++ moulinette/interfaces/__init__.py | 2 ++ 2 files changed, 4 insertions(+) diff --git a/moulinette/actionsmap.py b/moulinette/actionsmap.py index 11f7e2be..da2df8e0 100644 --- a/moulinette/actionsmap.py +++ b/moulinette/actionsmap.py @@ -563,6 +563,8 @@ class ActionsMap(object): ) func = getattr(mod, func_name) except (AttributeError, ImportError): + import traceback + traceback.print_exc() logger.exception("unable to load function %s.%s", namespace, func_name) raise MoulinetteError("error_see_log") else: diff --git a/moulinette/interfaces/__init__.py b/moulinette/interfaces/__init__.py index 4f2e33dd..08b0cb98 100644 --- a/moulinette/interfaces/__init__.py +++ b/moulinette/interfaces/__init__.py @@ -361,6 +361,8 @@ class _CallbackAction(argparse.Action): mod = __import__(mod_name, globals=globals(), level=0, fromlist=[func_name]) func = getattr(mod, func_name) except (AttributeError, ImportError): + import traceback + traceback.print_exc() raise ValueError("unable to import method {0}".format(self.callback_method)) self._callback = func From 01e064b642f965b231aff32e82a136c68faa676b Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 7 May 2020 19:33:27 +0200 Subject: [PATCH 43/91] Typo :| --- moulinette/authenticators/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/moulinette/authenticators/__init__.py b/moulinette/authenticators/__init__.py index fa0feb45..434dd6d7 100644 --- a/moulinette/authenticators/__init__.py +++ b/moulinette/authenticators/__init__.py @@ -174,7 +174,7 @@ class BaseAuthenticator(object): def _authenticate_session(self, session_id, session_token): """Checks session and token against the stored session token""" - if not self._session_exists(self, session_id): + if not self._session_exists(session_id): raise MoulinetteError("session_expired") try: # FIXME : shouldn't we also add a check that this session file From f2e9f26e5220220b6ff86ee3a0670bcd118146c2 Mon Sep 17 00:00:00 2001 From: Bram Date: Thu, 7 May 2020 21:53:38 +0200 Subject: [PATCH 44/91] Update moulinette/authenticators/ldap.py Co-authored-by: Alexandre Aubin --- moulinette/authenticators/ldap.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/moulinette/authenticators/ldap.py b/moulinette/authenticators/ldap.py index 75554a8c..992858c9 100644 --- a/moulinette/authenticators/ldap.py +++ b/moulinette/authenticators/ldap.py @@ -92,7 +92,7 @@ class Authenticator(BaseAuthenticator): raise MoulinetteError("invalid_password") except ldap.SERVER_DOWN: # ldap is down, attempt to restart it before really failing - logger.warning(m18n.g("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 From ab95a53bfc1bae2129815358b2df2266ba1a4c33 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 9 May 2020 21:08:54 +0200 Subject: [PATCH 45/91] Unecessary markdown in changelog --- debian/changelog | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/debian/changelog b/debian/changelog index b24f0c33..4119d8f6 100644 --- a/debian/changelog +++ b/debian/changelog @@ -44,21 +44,21 @@ moulinette (3.7.0) testing; urgency=low # ~ Major stuff - - [enh] Add group and permission mechanism ([Moulinette#189](https://github.com/YunoHost/moulinette/pull/189) - - [mod] Be able to customize prompt colors ([Moulinette/808f620](https://github.com/YunoHost/Moulinette/commit/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] 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] Add group and permission mechanism (#189) + - [mod] Be able to customize prompt colors (808f620) + - [enh] Support app manifests in toml (#204, 55515cb) + - [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 # 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] Add a write_to_yaml utility similar to write_to_json ([Moulinette/2e2e627](https://github.com/YunoHost/Moulinette/commit/2e2e627)) - - [enh] Warn the user about long locks ([Moulinette#205](https://github.com/YunoHost/moulinette/pull/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)) - - [fix] Misc micro bugfixes or improvements ([Moulinette/83d9e77](https://github.com/YunoHost/Moulinette/commit/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)) - - [enh] READMEs improvements ([Moulinette/1541b74](https://github.com/YunoHost/Moulinette/commit/1541b74), [Moulinette/ad1eeef](https://github.com/YunoHost/Moulinette/commit/ad1eeef)) + - [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 (2e2e627) + - [enh] Warn the user about long locks (#205) + - [mod] Tweak stuff about setuptools and moulinette deps? (b739f27, da00fc9, d8cbbb0) + - [fix] Misc micro bugfixes or improvements (83d9e77) + - [doc] Fix doc building + add doc build tests with Tox (f1ac5b8, df7d478, 74c8f79, bcf92c7, af2c80c, d52a574, 307f660, dced104, ed3823b) + - [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 From 9c8ddee09a6434ebf4dc526274e94697ccf79ef4 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 9 May 2020 21:16:25 +0200 Subject: [PATCH 46/91] Update changelog for 3.8.1 --- debian/changelog | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/debian/changelog b/debian/changelog index 4119d8f6..f100e0d6 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,13 @@ +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 Sat, 09 May 2020 21:09:35 +0200 + moulinette (3.8.0) testing; urgency=low # Major stuff From f967bed370ba8a89528e766a0dce623d03ab7094 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 12 May 2020 18:03:59 +0200 Subject: [PATCH 47/91] Locale lib sometimes miserably fails to parse locale ~.~ --- moulinette/interfaces/cli.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/moulinette/interfaces/cli.py b/moulinette/interfaces/cli.py index 804028bc..aa339866 100644 --- a/moulinette/interfaces/cli.py +++ b/moulinette/interfaces/cli.py @@ -169,8 +169,13 @@ def pretty_print_dict(d, depth=0): def get_locale(): - """Return current user locale""" - lang = locale.getdefaultlocale()[0] + """Return current user eocale""" + 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: return "" return lang[:2] From 9f69f04e3f57ffb0fb13a31b055d8cf7b7244b18 Mon Sep 17 00:00:00 2001 From: Kay0u Date: Tue, 12 May 2020 19:13:30 +0200 Subject: [PATCH 48/91] fix import mock --- test/test_actionsmap.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/test/test_actionsmap.py b/test/test_actionsmap.py index b69b5179..bf45adda 100644 --- a/test/test_actionsmap.py +++ b/test/test_actionsmap.py @@ -253,11 +253,18 @@ def test_actions_map_import_error(mocker): 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: amap.process({}, timeout=30, route=("GET", "/test-auth/none")) - mocker.stopall() translation = m18n.g("error_see_log") expected_msg = translation.format() assert expected_msg in str(exception) From 7fed0e5051ac9df68855394f5c34c6f1c38a1e05 Mon Sep 17 00:00:00 2001 From: Kay0u Date: Tue, 12 May 2020 19:13:37 +0200 Subject: [PATCH 49/91] fix linter --- moulinette/actionsmap.py | 1 + moulinette/interfaces/__init__.py | 1 + moulinette/interfaces/cli.py | 2 +- 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/moulinette/actionsmap.py b/moulinette/actionsmap.py index da2df8e0..8170e95a 100644 --- a/moulinette/actionsmap.py +++ b/moulinette/actionsmap.py @@ -564,6 +564,7 @@ class ActionsMap(object): func = getattr(mod, func_name) except (AttributeError, ImportError): import traceback + traceback.print_exc() logger.exception("unable to load function %s.%s", namespace, func_name) raise MoulinetteError("error_see_log") diff --git a/moulinette/interfaces/__init__.py b/moulinette/interfaces/__init__.py index 08b0cb98..e1650e97 100644 --- a/moulinette/interfaces/__init__.py +++ b/moulinette/interfaces/__init__.py @@ -362,6 +362,7 @@ class _CallbackAction(argparse.Action): func = getattr(mod, func_name) except (AttributeError, ImportError): import traceback + traceback.print_exc() raise ValueError("unable to import method {0}".format(self.callback_method)) self._callback = func diff --git a/moulinette/interfaces/cli.py b/moulinette/interfaces/cli.py index aa339866..1b8e0ef9 100644 --- a/moulinette/interfaces/cli.py +++ b/moulinette/interfaces/cli.py @@ -175,7 +175,7 @@ def get_locale(): 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') + lang = os.getenv("LANG") if not lang: return "" return lang[:2] From f2370c0444c55fcb853a73364829e2de83b26b68 Mon Sep 17 00:00:00 2001 From: Kay0u Date: Tue, 12 May 2020 19:29:13 +0200 Subject: [PATCH 50/91] don't restart ldap in tests --- test/test_ldap.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/test/test_ldap.py b/test/test_ldap.py index fadff9e6..13fff019 100644 --- a/test/test_ldap.py +++ b/test/test_ldap.py @@ -66,11 +66,14 @@ class TestLDAP: 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"]["user_rdn"] = "cn=admin,dc=yunohost,dc=org" ldap_server.stop() 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: ldap_interface.authenticate(password="yunohost") From 230f90741417ddb3cc101d7bb33bd75078c4821b Mon Sep 17 00:00:00 2001 From: amirale qt Date: Mon, 27 Apr 2020 07:40:33 +0000 Subject: [PATCH 51/91] Translated using Weblate (Chinese (Simplified)) Currently translated at 100.0% (55 of 55 strings) Translation: YunoHost/moulinette Translate-URL: https://translate.yunohost.org/projects/yunohost/moulinette/zh_Hans/ --- locales/cmn.json | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/locales/cmn.json b/locales/cmn.json index e9b8b52d..e5632fc9 100644 --- a/locales/cmn.json +++ b/locales/cmn.json @@ -14,13 +14,13 @@ "folder_exists": "目录已存在:{path}", "folder_not_exist": "目录不存在", "info": "信息:", - "instance_already_running": "实例已正在运行", + "instance_already_running": "已经有一个YunoHost操作正在运行。 请等待它完成再运行另一个。", "invalid_argument": "参数错误{argument}:{error}", "invalid_password": "密码错误", "invalid_usage": "用法错误,输入 --help 查看帮助信息", "ldap_attribute_already_exists": "参数{attribute}已赋值{value}", "ldap_server_down": "无法连接LDAP服务器", - "logged_in": "登录成功", + "logged_in": "登录", "logged_out": "登出", "not_logged_in": "您未登录", "operation_interrupted": "操作中断", @@ -30,7 +30,7 @@ "server_already_running": "服务已运行在指定端口", "success": "成功!", "unable_authenticate": "认证失败", - "unable_retrieve_session": "获取会话失败", + "unable_retrieve_session": "由于“ {exception}”,无法检索会话", "unknown_group": "未知组{group}", "unknown_user": "未知用户{user}", "values_mismatch": "值不匹配", @@ -38,7 +38,7 @@ "websocket_request_expected": "期望一个WebSocket请求", "cannot_open_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_yaml": "读取yaml文件{ressource:s}失败(原因:{error:s})", "error_writing_file": "写入文件{file:s}失败:{error:s}", @@ -49,5 +49,10 @@ "download_timeout": "{url:s}响应超时,放弃。", "download_unknown_error": "下载{url:s}失败:{error: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": "令牌无效-请进行身份验证" } From cfad73862264ca97e610533cf670b20e21fc0a8f Mon Sep 17 00:00:00 2001 From: amirale qt Date: Mon, 27 Apr 2020 07:44:39 +0000 Subject: [PATCH 52/91] Translated using Weblate (Nepali) Currently translated at 16.4% (9 of 55 strings) Translation: YunoHost/moulinette Translate-URL: https://translate.yunohost.org/projects/yunohost/moulinette/ne/ --- locales/ne.json | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/locales/ne.json b/locales/ne.json index 3f07aa14..457005f4 100644 --- a/locales/ne.json +++ b/locales/ne.json @@ -1,4 +1,11 @@ { "logged_out": "लग आउट", - "password": "पासवर्ड" + "password": "पासवर्ड", + "deprecated_command_alias": "'{prog} {old}' अस्वीकृत गरिएको छ र भविष्यमा हटाइनेछ, यसको सट्टा '{prog} {new}' प्रयोग गर्नुहोस्।", + "deprecated_command": "'{prog} {command}' अस्वीकृत गरिएको छ र भविष्यमा हटाइनेछ", + "confirm": "कन्फर्म {prompt}", + "colon": "{}: ", + "authentication_required_long": "यस कार्य गर्नको लागि प्रमाणीकरण आवाश्यक हुन्छ", + "authentication_required": "प्रमाणीकरण आवाश्यक छ", + "argument_required": "तर्क '{argument}' आवश्यक छ" } From 5cdbbb330bc705fd43076463bdf4121016b258fd Mon Sep 17 00:00:00 2001 From: xaloc33 Date: Sat, 9 May 2020 18:42:20 +0000 Subject: [PATCH 53/91] Translated using Weblate (Catalan) Currently translated at 100.0% (57 of 57 strings) Translation: YunoHost/moulinette Translate-URL: https://translate.yunohost.org/projects/yunohost/moulinette/ca/ --- locales/ca.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/locales/ca.json b/locales/ca.json index bde04548..1060ba5b 100644 --- a/locales/ca.json +++ b/locales/ca.json @@ -55,5 +55,7 @@ "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_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." } From 48b1c6c81245f0de4f6e431294a9e83a7f5c8d01 Mon Sep 17 00:00:00 2001 From: clecle226 Date: Sun, 10 May 2020 13:46:52 +0000 Subject: [PATCH 54/91] Translated using Weblate (French) Currently translated at 100.0% (57 of 57 strings) Translation: YunoHost/moulinette Translate-URL: https://translate.yunohost.org/projects/yunohost/moulinette/fr/ --- locales/fr.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/locales/fr.json b/locales/fr.json index b589d621..67066934 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -55,5 +55,7 @@ "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_that_lock_is_acquired": "l’autre commande vient de se terminer, lancement de cette 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 éteint, nous tentons de le redémarrer...", + "session_expired": "La session a expiré. Merci de vous ré-authentifier." } From dcb57adf320d95473239373b062aa395fb2b2f1c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Quent=C3=AD?= Date: Mon, 18 May 2020 20:21:43 +0000 Subject: [PATCH 55/91] Translated using Weblate (Occitan) Currently translated at 100.0% (57 of 57 strings) Translation: YunoHost/moulinette Translate-URL: https://translate.yunohost.org/projects/yunohost/moulinette/oc/ --- locales/oc.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/locales/oc.json b/locales/oc.json index 5db9677f..d15dab2c 100644 --- a/locales/oc.json +++ b/locales/oc.json @@ -55,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_again": "Encara en espèra…", "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." } From 7e8f1af2fa8a631a4c6ee9752ee0ef4e644da2b5 Mon Sep 17 00:00:00 2001 From: Kay0u Date: Wed, 20 May 2020 19:00:40 +0000 Subject: [PATCH 56/91] Update changelog for 3.8.1.1 release --- debian/changelog | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/debian/changelog b/debian/changelog index cc42a4cd..405e29ea 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,9 @@ +moulinette (3.8.1.1) stable; urgency=low + + Bumping version number for stable release + + -- Kay0u 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) From c97e8bab058fb8e6490d7056ffd929ece1339c89 Mon Sep 17 00:00:00 2001 From: Kay0u Date: Fri, 22 May 2020 08:02:57 +0000 Subject: [PATCH 57/91] Update changelog for 3.8.1.2 release --- debian/changelog | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/debian/changelog b/debian/changelog index 405e29ea..d45d20b7 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,12 @@ +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 Fri, 22 May 2020 07:58:19 +0000 + moulinette (3.8.1.1) stable; urgency=low Bumping version number for stable release From 758b0913d99f77b53deb9d96a72c560b5d236cd7 Mon Sep 17 00:00:00 2001 From: ButterflyOfFire Date: Thu, 21 May 2020 12:44:31 +0000 Subject: [PATCH 58/91] Translated using Weblate (Arabic) Currently translated at 98.2% (56 of 57 strings) Translation: YunoHost/moulinette Translate-URL: https://translate.yunohost.org/projects/yunohost/moulinette/ar/ --- locales/ar.json | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/locales/ar.json b/locales/ar.json index 260d614f..f54a6127 100644 --- a/locales/ar.json +++ b/locales/ar.json @@ -52,6 +52,9 @@ "corrupted_yaml": "قراءة مُشوّهة لنسق yaml مِن {ressource:s} (السبب : {error:s})", "info": "معلومة:", "warn_the_user_about_waiting_lock_again": "جارٍ الانتظار…", - "warn_the_user_that_lock_is_acquired": "لقد انتهى تنفيذ ذاك الأمر ، جارٍ إطلاق الأمر", - "warn_the_user_about_waiting_lock": "هناك أمر لـ YunoHost قيد التشغيل حاليا. في انتظار انتهاء تنفيذه قبل تشغيل التالي" + "warn_the_user_that_lock_is_acquired": "لقد انتهى تنفيذ ذاك الأمر للتوّ ، جارٍ تنفيذ هذا الأمر", + "warn_the_user_about_waiting_lock": "هناك أمر لـ YunoHost قيد التشغيل حاليا. في انتظار انتهاء تنفيذه قبل تشغيل التالي", + "ldap_server_is_down_restart_it": "إنّ خدمة LDAP غير مشغّلة ، نحن بصدد محاولة إعادة تشغيلها…", + "session_expired": "لقد انتهت مدة صلاحية الجلسة. رجاءً أعد الإستيثاق.", + "invalid_token": "إنّ الرمز المميز غير صالح - يرجى الإستيثاق" } From b05c8061e24891786fbfdcac36adc635074db85b Mon Sep 17 00:00:00 2001 From: Leandro Noferini Date: Sun, 24 May 2020 21:35:28 +0000 Subject: [PATCH 59/91] Translated using Weblate (Italian) Currently translated at 100.0% (57 of 57 strings) Translation: YunoHost/moulinette Translate-URL: https://translate.yunohost.org/projects/yunohost/moulinette/it/ --- locales/it.json | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/locales/it.json b/locales/it.json index fa228abc..fdb6d2b9 100644 --- a/locales/it.json +++ b/locales/it.json @@ -51,9 +51,11 @@ "download_bad_status_code": "{url:s} ha restituito il codice di stato {code:s}", "command_unknown": "Comando '{command:s}' sconosciuto ?", "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": "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})", - "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, riprovo ad avviarlo..." } From 7818f07ed15db2bdc4bba7246012f971cc367f40 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 26 May 2020 23:53:02 +0200 Subject: [PATCH 60/91] Update authorship/maintainer information --- debian/control | 11 +++++------ moulinette/__init__.py | 3 +-- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/debian/control b/debian/control index b98a13e5..ade9ba77 100644 --- a/debian/control +++ b/debian/control @@ -1,7 +1,7 @@ Source: moulinette Section: python Priority: optional -Maintainer: Jérôme Lebleu +Maintainer: YunoHost Contributors Build-Depends: debhelper (>= 9), python (>= 2.7), dh-python, python-setuptools, python-psutil, python-all (>= 2.7) Standards-Version: 3.9.6 X-Python-Version: >= 2.7 @@ -21,9 +21,8 @@ Depends: ${misc:Depends}, ${python:Depends}, Replaces: yunohost-cli Breaks: yunohost-cli Description: prototype interfaces with ease in Python - The moulinette is a Python package that allows one to quickly and - easily prototype interfaces for your application. Each action can - be served through an HTTP API and from the command-line with a single - method. + Quickly and easily prototype interfaces for your application. + Each action can be served through an HTTP API and from the + command-line with a single method. . - It was originally written for the YunoHost project. + Originally designed and written for the YunoHost project. diff --git a/moulinette/__init__.py b/moulinette/__init__.py index 4aae1be0..a1aec5e2 100755 --- a/moulinette/__init__.py +++ b/moulinette/__init__.py @@ -9,8 +9,7 @@ from moulinette.core import ( from moulinette.globals import init_moulinette_env __title__ = "moulinette" -__version__ = "0.1" -__author__ = ["Kload", "jlebleu", "titoko", "beudbeud", "npze"] +__author__ = ["Yunohost Contributors"] __license__ = "AGPL 3.0" __credits__ = """ Copyright (C) 2014 YUNOHOST.ORG From f08c239747a075bc301e4556a7382b1e6ec8d926 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89ric=20Gaspar?= <46165813+ericgaspar@users.noreply.github.com> Date: Fri, 17 Jul 2020 23:55:39 +0200 Subject: [PATCH 61/91] Update en.json - *Typos* --- locales/en.json | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/locales/en.json b/locales/en.json index be6a8015..7558e910 100644 --- a/locales/en.json +++ b/locales/en.json @@ -41,20 +41,20 @@ "cannot_open_file": "Could not open 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})", - "corrupted_json": "Corrupted json 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})", - "error_writing_file": "Error when writing file {file:s}: {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_toml": "Corrupted TOML read from {ressource:s} (reason: {error:s})", + "error_writing_file": "Error when writing file {file: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}", - "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_timeout": "{url:s} took too long to answer, gave up.", "download_unknown_error": "Error when downloading data from {url:s}: {error: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_again": "Still waiting...", - "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..." + "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..." } From 6daaee9b0b6a160f608f1d2c7acf9d1c36958cf8 Mon Sep 17 00:00:00 2001 From: ppr Date: Sun, 19 Jul 2020 12:27:08 +0000 Subject: [PATCH 62/91] Translated using Weblate (French) Currently translated at 94.7% (54 of 57 strings) Translation: YunoHost/moulinette Translate-URL: https://translate.yunohost.org/projects/yunohost/moulinette/fr/ --- locales/fr.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/locales/fr.json b/locales/fr.json index 67066934..848140d1 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -54,8 +54,8 @@ "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_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": "L’autre commande vient de se terminer, lancement de cette commande", "invalid_token": "Jeton non valide - veuillez vous authentifier", - "ldap_server_is_down_restart_it": "Le service LDAP est éteint, nous tentons de le redémarrer...", + "ldap_server_is_down_restart_it": "Le service LDAP est arrêté, essayez de le redémarrer ...", "session_expired": "La session a expiré. Merci de vous ré-authentifier." } From 81f5bca81f510dee11a97971376baf15bc7aa86c Mon Sep 17 00:00:00 2001 From: xaloc33 Date: Thu, 23 Jul 2020 17:07:29 +0000 Subject: [PATCH 63/91] Translated using Weblate (Catalan) Currently translated at 100.0% (57 of 57 strings) Translation: YunoHost/moulinette Translate-URL: https://translate.yunohost.org/projects/yunohost/moulinette/ca/ --- locales/ca.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/locales/ca.json b/locales/ca.json index 1060ba5b..603b841e 100644 --- a/locales/ca.json +++ b/locales/ca.json @@ -39,23 +39,23 @@ "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})", "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_yaml": "Yaml 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})", "error_writing_file": "Error al escriure el fitxer {file: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}", - "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_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_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:", "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_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", - "ldap_server_is_down_restart_it": "el servei ldap està caigut, s'està intentant tornar-lo a engegar…", + "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." } From 14f9e8c55f5771ca807b53ec86ccc710280fa8aa Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 27 Jul 2020 15:53:34 +0200 Subject: [PATCH 64/91] Update locales/fr.json --- locales/fr.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/locales/fr.json b/locales/fr.json index 848140d1..c4ff6609 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -56,6 +56,6 @@ "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", "invalid_token": "Jeton non valide - veuillez vous authentifier", - "ldap_server_is_down_restart_it": "Le service LDAP est arrêté, essayez de le redémarrer ...", + "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." } From 2b35b918f40f01f244932956a615514a31c6a0ef Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 27 Jul 2020 17:33:52 +0200 Subject: [PATCH 65/91] Update changelog for 3.8.1.3 --- debian/changelog | 29 +++++++++++++++++++---------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/debian/changelog b/debian/changelog index d45d20b7..9a57c764 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,9 +1,18 @@ +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 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 + Thanks to all contributors ! (Aleks, amirale qt, clecle226, Quentí, xaloc33) -- Kay0u Fri, 22 May 2020 07:58:19 +0000 @@ -49,7 +58,7 @@ moulinette (3.7.1.1) stable; urgency=low - [fix] Report actual errors when some LDAP operation fails to ease debugging - + -- Alexandre Aubin Fri, 17 Apr 2020 17:00:00 +0000 moulinette (3.7.1) stable; urgency=low @@ -68,7 +77,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) - Thanks to all contributors (Josue) <3 ! + Thanks to all contributors (Josué) <3 ! -- Kay0u Sun, 15 Mar 2020 16:09:25 +0000 @@ -140,7 +149,7 @@ moulinette (3.5.1) testing; urgency=low * [fix] Fix case where stdinfo is not provided in call_async_output (0a300e5) * [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 Wed, 03 Apr 2019 02:25:00 +0000 @@ -148,7 +157,7 @@ moulinette (3.5.0) testing; urgency=low * [i18n] Improve Russian and Chinese (Mandarin) translations - Contributors : n3uz, Алексей + Contributors : n3uz, Алексей -- Alexandre Aubin Wed, 13 Mar 2019 17:20:00 +0000 @@ -261,7 +270,7 @@ moulinette (2.7.13) testing; urgency=low * [i18n] Improve translations for Portugueuse, Occitan * [enh] Add read_yaml util (#161) - Contributors : Bram, by0ne, Quent-in + Contributors : Bram, by0ne, Quentí -- Alexandre Aubin Mon, 28 May 2018 02:55:00 +0000 @@ -279,7 +288,7 @@ moulinette (2.7.11) testing; urgency=low * [fix] Avoid cases where get_cookie returns None * [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 Tue, 01 May 2018 23:33:59 +0000 @@ -351,7 +360,7 @@ moulinette (2.7.0) testing; urgency=low * [enh] Show description of command in --help (#148) * [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 Mon, 07 Aug 2017 13:04:21 -0400 @@ -367,9 +376,9 @@ moulinette (2.6.0) testing; urgency=low * [fix] Use ordered dict for the actionmap cache (#136) * [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 Mon, 24 Apr 2017 12:44:23 -0400 From 024cadf42680a2c2c3b7f31cda565dbd670bffa0 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 12 Aug 2019 16:17:33 +0200 Subject: [PATCH 66/91] Get rid of legacy code which breaks postinstall on buster for some reason --- moulinette/authenticators/ldap.py | 27 --------------------------- 1 file changed, 27 deletions(-) diff --git a/moulinette/authenticators/ldap.py b/moulinette/authenticators/ldap.py index 992858c9..860a45c9 100644 --- a/moulinette/authenticators/ldap.py +++ b/moulinette/authenticators/ldap.py @@ -4,9 +4,6 @@ from __future__ import absolute_import import os import logging -import random -import string -import crypt import ldap import ldap.sasl import time @@ -114,30 +111,6 @@ class Authenticator(BaseAuthenticator): raise MoulinetteError("Not logged in with the expected userdn ?!") else: 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 # TODO: Review these methods From af3da58ed485ff9ac81faf11a8a8908f4d04a5c9 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 7 Feb 2020 22:30:23 +0100 Subject: [PATCH 67/91] Remove legacy Breaks and Replaces -- it is safe to do so because 3.x instances already have these Conflicts / Replaces, so it's okay to remove them for 4.x --- debian/control | 2 -- 1 file changed, 2 deletions(-) diff --git a/debian/control b/debian/control index ade9ba77..897747db 100644 --- a/debian/control +++ b/debian/control @@ -18,8 +18,6 @@ Depends: ${misc:Depends}, ${python:Depends}, python-toml, python-psutil, python-tz -Replaces: yunohost-cli -Breaks: yunohost-cli Description: prototype interfaces with ease in Python Quickly and easily prototype interfaces for your application. Each action can be served through an HTTP API and from the From d40a91ab9beaf15e3a0f0cb728ab20b916e01c17 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 1 Apr 2020 22:12:13 +0200 Subject: [PATCH 68/91] Let's hash the password like we do in core during tests --- test/src/ldap_server.py | 17 ++++++++++++++++- tox.ini | 1 + 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/test/src/ldap_server.py b/test/src/ldap_server.py index 710f04a6..e34f6f38 100644 --- a/test/src/ldap_server.py +++ b/test/src/ldap_server.py @@ -98,7 +98,22 @@ class LDAPServer: "posixAccount", "simpleSecurityObject", ], - "userPassword": ["yunohost"], + "userPassword": [self._hash_user_password("yunohost")], } 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) diff --git a/tox.ini b/tox.ini index 85021c0d..dc175f71 100644 --- a/tox.ini +++ b/tox.ini @@ -19,6 +19,7 @@ deps = gevent-websocket bottle >= 0.12 WebTest >= 2.0, < 2.1 + python-ldap >= 3.1.0 commands = pytest {posargs} From 92eeb05971eca63e276b7a027b7fb0b1015e0348 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 5 Jun 2020 17:33:24 +0200 Subject: [PATCH 69/91] Update changelog for 4.0.1~alpha --- debian/changelog | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/debian/changelog b/debian/changelog index 9a57c764..bdab84d2 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,11 @@ +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 Fri, 05 Jun 2020 17:32:35 +0200 + moulinette (3.8.1.3) stable; urgency=low - Update authorship/maintainer information (7818f07) From bf866727bcc1d53515d4a0283dd66a6a21555af3 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 19 Jun 2020 15:34:10 +0200 Subject: [PATCH 70/91] Bump version to 4.0.2~beta --- debian/changelog | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/debian/changelog b/debian/changelog index bdab84d2..5af9c16c 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,9 @@ +moulinette (4.0.2~beta) testing; urgency=low + + - Bump version number for beta release + + -- Alexandre Aubin 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) From 711c2114409babbfb4c7c5a465652b517584d338 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 29 Jul 2020 17:03:04 +0200 Subject: [PATCH 71/91] Update changelog for 4.0.3 --- debian/changelog | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/debian/changelog b/debian/changelog index 5af9c16c..af161699 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,9 @@ +moulinette (4.0.3) stable; urgency=low + + - Bump version number for stable release + + -- Alexandre Aubin Wed, 29 Jul 2020 17:00:00 +0200 + moulinette (4.0.2~beta) testing; urgency=low - Bump version number for beta release From dbc4716b485839fec4b19d6dadebeafb2eea9aec Mon Sep 17 00:00:00 2001 From: Kay0u Date: Fri, 14 Aug 2020 17:54:38 +0200 Subject: [PATCH 72/91] Fix tests --- test/src/ldap_server.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/test/src/ldap_server.py b/test/src/ldap_server.py index e34f6f38..86a687f8 100644 --- a/test/src/ldap_server.py +++ b/test/src/ldap_server.py @@ -103,7 +103,6 @@ class LDAPServer: ldap_interface.update("cn=admin", admin_dict) - def _hash_user_password(self, password): """ Copy pasta of what's in yunohost/user.py @@ -112,8 +111,10 @@ class LDAPServer: 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)]) + 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) + salt = "$6$" + salt + "$" + return "{CRYPT}" + crypt.crypt(str(password), salt) From 2aca0a81837893745a156b6d2d877234d8ed76db Mon Sep 17 00:00:00 2001 From: Kay0u Date: Thu, 20 Aug 2020 00:42:36 +0200 Subject: [PATCH 73/91] update tox config --- .travis.yml | 13 ++++++++++--- setup.py | 2 +- test/conftest.py | 2 +- tox.ini | 40 ++++++++++++++++++---------------------- 4 files changed, 30 insertions(+), 27 deletions(-) diff --git a/.travis.yml b/.travis.yml index fde3108c..85584d17 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,12 +7,19 @@ addons: - slapd matrix: + allow_failures: + - env: TOXENV=py37-pytest + - env: TOXENV=py37-lint include: - python: 2.7 - env: TOXENV=py27 + env: TOXENV=py27-pytest - python: 2.7 - env: TOXENV=lint - - python: 3.6 + env: TOXENV=py27-lint + - python: 3.7 + env: TOXENV=py37-pytest + - python: 3.7 + env: TOXENV=py37-lint + - python: 3.7 env: TOXENV=format-check - python: 2.7 env: TOXENV=docs diff --git a/setup.py b/setup.py index b8d8024f..736ad010 100755 --- a/setup.py +++ b/setup.py @@ -27,7 +27,7 @@ setup(name='Moulinette', license='AGPL', packages=find_packages(exclude=['test']), data_files=[(LOCALES_DIR, locale_files)], - python_requires='==2.7.*', + python_requires='>=2.7.*', install_requires=[ 'argcomplete', 'psutil', diff --git a/test/conftest.py b/test/conftest.py index 6df66806..94f5f64f 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -7,7 +7,7 @@ import os import shutil import pytest -from src.ldap_server import LDAPServer +from .src.ldap_server import LDAPServer def patch_init(moulinette): diff --git a/tox.ini b/tox.ini index dc175f71..fdb9e211 100644 --- a/tox.ini +++ b/tox.ini @@ -1,7 +1,8 @@ [tox] -envlist = - py27 - lint +envlist = + py{27,3}-{pytest,lint} + format + format-check docs skipdist = True @@ -9,26 +10,21 @@ skipdist = True usedevelop = True passenv = * deps = - pytest >= 4.6.3, < 5.0 - pytest-cov >= 2.7.1, < 3.0 - 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 - python-ldap >= 3.1.0 + py{27,3}-pytest: pytest >= 4.6.3, < 5.0 + pytest-cov >= 2.7.1, < 3.0 + 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 + python-ldap >= 3.1.0 + py{27,3}-lint: flake8 commands = - pytest {posargs} - -[testenv:lint] -commands = flake8 moulinette test -deps = flake8 -skip_install = True -usedevelop = False - + py{27,3}-pytest: pytest {posargs} -c pytest.ini + py{27,3}-lint: flake8 moulinette test [testenv:format] basepython = python3 From d9fa6c7858c2b921c3da38b68f15af475b3f2cdf Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 21 Aug 2020 16:40:19 +0200 Subject: [PATCH 74/91] Fix api18n : it should return a wrapper and not the callback directly. Current code led to some weird incorrect locale sometimes --- moulinette/interfaces/api.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/moulinette/interfaces/api.py b/moulinette/interfaces/api.py index afa6f081..03a77959 100644 --- a/moulinette/interfaces/api.py +++ b/moulinette/interfaces/api.py @@ -776,12 +776,14 @@ class Interface(BaseInterface): # Attempt to retrieve and set locale def api18n(callback): - try: - locale = request.params.pop("locale") - except KeyError: - locale = m18n.default_locale - m18n.set_locale(locale) - return callback + def wrapper(*args, **kwargs): + try: + locale = request.params.pop("locale") + except KeyError: + locale = m18n.default_locale + m18n.set_locale(locale) + return callback(*args, **kwargs) + return wrapper # Install plugins app.install(filter_csrf) From be6ff8e12a3a5454611f1f55da1dfcdce567e6a3 Mon Sep 17 00:00:00 2001 From: Kayou Date: Fri, 21 Aug 2020 17:21:49 +0200 Subject: [PATCH 75/91] code format --- moulinette/interfaces/api.py | 1 + 1 file changed, 1 insertion(+) diff --git a/moulinette/interfaces/api.py b/moulinette/interfaces/api.py index 03a77959..fbad933a 100644 --- a/moulinette/interfaces/api.py +++ b/moulinette/interfaces/api.py @@ -783,6 +783,7 @@ class Interface(BaseInterface): locale = m18n.default_locale m18n.set_locale(locale) return callback(*args, **kwargs) + return wrapper # Install plugins From 9609fe12101f3f358f963673da912d7d6ac0a9cb Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 27 Aug 2020 16:55:50 +0200 Subject: [PATCH 76/91] Prevent installing moulinette 4.1 without yunohost 4.1 which may result in broken setups --- debian/control | 1 + 1 file changed, 1 insertion(+) diff --git a/debian/control b/debian/control index 897747db..6554ec62 100644 --- a/debian/control +++ b/debian/control @@ -18,6 +18,7 @@ Depends: ${misc:Depends}, ${python:Depends}, python-toml, python-psutil, python-tz +Breaks: yunohost (<< 4.1) Description: prototype interfaces with ease in Python Quickly and easily prototype interfaces for your application. Each action can be served through an HTTP API and from the From 2501ecda56bd6dcdde2fc863be5352d66d00b225 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 27 Aug 2020 19:21:48 +0200 Subject: [PATCH 77/91] Ugly hack to have the name of the main logger, otherwise error/exception messages ain't displayed --- moulinette/__init__.py | 7 +++---- moulinette/utils/log.py | 1 + 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/moulinette/__init__.py b/moulinette/__init__.py index 9ed0220c..1bb9b818 100755 --- a/moulinette/__init__.py +++ b/moulinette/__init__.py @@ -90,12 +90,12 @@ def api(host="localhost", port=80, routes={}): except MoulinetteError as e: import logging - logging.getLogger().error(e.strerror) + logging.getLogger(logging.main_logger).error(e.strerror) return 1 except KeyboardInterrupt: import logging - logging.getLogger().info(m18n.g("operation_interrupted")) + logging.getLogger(logging.main_logger).info(m18n.g("operation_interrupted")) return 0 @@ -121,8 +121,7 @@ def cli(args, top_parser, output_as=None, timeout=None): ) except MoulinetteError as e: import logging - - logging.getLogger().error(e.strerror) + logging.getLogger(logging.main_logger).error(e.strerror) return 1 return 0 diff --git a/moulinette/utils/log.py b/moulinette/utils/log.py index 16a3fc23..e1a902b5 100644 --- a/moulinette/utils/log.py +++ b/moulinette/utils/log.py @@ -65,6 +65,7 @@ def configure_logging(logging_config=None): # load configuration from dict dictConfig(DEFAULT_LOGGING) if logging_config: + logging.main_logger = logging_config.get("main_logger") dictConfig(logging_config) From d3b598ce387cdfc2bd055a20d2922027abeaf2f2 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 28 Aug 2020 18:36:49 +0200 Subject: [PATCH 78/91] Bump version number to 4.1 to fix CI / tests --- debian/changelog | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/debian/changelog b/debian/changelog index af161699..e23978dc 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,7 @@ +moulinette (4.1) unstable; urgency=low + + - Tmp bump version number for CI / tests + moulinette (4.0.3) stable; urgency=low - Bump version number for stable release From 923e394d0b4dd81213b96039ebe4c66e2efc9c85 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 6 Sep 2020 21:24:50 +0200 Subject: [PATCH 79/91] Allow python functions to return a raw HTTPResponse --- moulinette/interfaces/api.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/moulinette/interfaces/api.py b/moulinette/interfaces/api.py index 439cbf2e..7f679c7d 100644 --- a/moulinette/interfaces/api.py +++ b/moulinette/interfaces/api.py @@ -577,6 +577,9 @@ def format_for_response(content): return "" response.status = 200 + if isinstance(content, HTTPResponse): + return content + # Return JSON-style response response.content_type = "application/json" return json_encode(content, cls=JSONExtendedEncoder) From b520549f920209f9c8d9ad33e0f5ee89536af097 Mon Sep 17 00:00:00 2001 From: Christian Wehrli Date: Tue, 1 Sep 2020 15:16:38 +0000 Subject: [PATCH 80/91] Translated using Weblate (German) Currently translated at 98.2% (56 of 57 strings) Translation: YunoHost/moulinette Translate-URL: https://translate.yunohost.org/projects/yunohost/moulinette/de/ --- locales/de.json | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/locales/de.json b/locales/de.json index 37496075..6062fabe 100644 --- a/locales/de.json +++ b/locales/de.json @@ -43,7 +43,7 @@ "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_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?", @@ -55,5 +55,7 @@ "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})" + "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." } From 10d3151042dcf4be021f09be23fa75927aeb9133 Mon Sep 17 00:00:00 2001 From: Leandro Noferini Date: Thu, 3 Sep 2020 17:19:01 +0000 Subject: [PATCH 81/91] Translated using Weblate (Italian) Currently translated at 96.5% (55 of 57 strings) Translation: YunoHost/moulinette Translate-URL: https://translate.yunohost.org/projects/yunohost/moulinette/it/ --- locales/it.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/locales/it.json b/locales/it.json index fdb6d2b9..e559de91 100644 --- a/locales/it.json +++ b/locales/it.json @@ -39,8 +39,8 @@ "cannot_open_file": "Impossibile aprire 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})", - "corrupted_json": "Lettura json corrotta da {ressource:s} (motivo: {error:s})", - "corrupted_yaml": "Lettura yaml corrotta da {ressource:s} (motivo: {error:s})", + "corrupted_json": "Lettura JSON corrotta da {resource: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_removing": "Errore durante la rimozione {path:s}: {error:s}", "error_changing_file_permissions": "Errore durante il cambio di permessi per {path:s}: {error:s}", @@ -49,12 +49,12 @@ "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_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:", - "warn_the_user_that_lock_is_acquired": "l'altro comando è appena completato, ora avvio 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": "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", "session_expired": "La sessione è terminata. Sei pregato di autenticarti nuovamente.", "ldap_server_is_down_restart_it": "Il servizio LDAP è terminato, riprovo ad avviarlo..." From 2818c75a03530ca01b6d035ae25efe148e221f25 Mon Sep 17 00:00:00 2001 From: Yifei Ding Date: Tue, 1 Sep 2020 20:58:51 +0000 Subject: [PATCH 82/91] Translated using Weblate (Chinese (Simplified)) Currently translated at 100.0% (57 of 57 strings) Translation: YunoHost/moulinette Translate-URL: https://translate.yunohost.org/projects/yunohost/moulinette/zh_Hans/ --- locales/cmn.json | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/locales/cmn.json b/locales/cmn.json index e5632fc9..3ae5a036 100644 --- a/locales/cmn.json +++ b/locales/cmn.json @@ -39,12 +39,12 @@ "cannot_open_file": "不能打开文件{file:s}(原因:{error:s})", "cannot_write_file": "写入文件{file:s}失败(原因:{error:s})", "unknown_error_reading_file": "尝试读取文件{files}时发生未知错误(原因:{errors})", - "corrupted_json": "json数据{ressource:s}读取失败(原因:{error:s})", - "corrupted_yaml": "读取yaml文件{ressource:s}失败(原因:{error:s})", + "corrupted_json": "从{ressource:s}读取的JSON损坏(原因:{error:s})", + "corrupted_yaml": "从{ressource:s}读取的YMAL损坏(原因:{error:s})", "error_writing_file": "写入文件{file:s}失败:{error:s}", "error_removing": "删除路径{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_timeout": "{url:s}响应超时,放弃。", "download_unknown_error": "下载{url:s}失败:{error:s}", @@ -53,6 +53,8 @@ "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": "令牌无效-请进行身份验证" + "corrupted_toml": "从{ressource:s}读取的TOML损坏(原因:{error:s})", + "invalid_token": "令牌无效-请进行身份验证", + "ldap_server_is_down_restart_it": "LDAP服务已下线,正在尝试重启服务……", + "session_expired": "会话已过期。请重新进行身份验证。" } From f01466d6efedc7578d62e89b54ebfb6c4a7a39de Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 31 Oct 2020 18:53:40 +0100 Subject: [PATCH 83/91] Always strip() the output of check_output --- moulinette/utils/process.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/moulinette/utils/process.py b/moulinette/utils/process.py index fd3d7e1a..ba3fd911 100644 --- a/moulinette/utils/process.py +++ b/moulinette/utils/process.py @@ -28,7 +28,7 @@ def check_output(args, stderr=subprocess.STDOUT, shell=True, **kwargs): 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 ---------------------------------------------- From 39446a83995e03d88b35018a45680f8145f493da Mon Sep 17 00:00:00 2001 From: Kayou Date: Wed, 2 Dec 2020 22:24:36 +0100 Subject: [PATCH 84/91] Update test_process.py --- test/test_process.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/test_process.py b/test/test_process.py index c18cdbe7..c3bc7cc8 100644 --- a/test/test_process.py +++ b/test/test_process.py @@ -115,6 +115,6 @@ def test_call_async_output_kwargs(test_file, mocker): 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" From c513d8459c51131b7bab8269c7cfd88aadabde7a Mon Sep 17 00:00:00 2001 From: ppr Date: Thu, 1 Oct 2020 17:42:10 +0000 Subject: [PATCH 85/91] Translated using Weblate (French) Currently translated at 98.2% (56 of 57 strings) Translation: YunoHost/moulinette Translate-URL: https://translate.yunohost.org/projects/yunohost/moulinette/fr/ --- locales/fr.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/locales/fr.json b/locales/fr.json index c4ff6609..2719373e 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -54,7 +54,7 @@ "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_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", "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." From 18b91d5f102633a65ed0eebc5395a0dde8ad9678 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20Villarreal?= Date: Thu, 5 Nov 2020 16:23:40 +0000 Subject: [PATCH 86/91] Translated using Weblate (Spanish) Currently translated at 100.0% (57 of 57 strings) Translation: YunoHost/moulinette Translate-URL: https://translate.yunohost.org/projects/yunohost/moulinette/es/ --- locales/es.json | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/locales/es.json b/locales/es.json index 399ad276..6a501082 100644 --- a/locales/es.json +++ b/locales/es.json @@ -39,21 +39,23 @@ "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})", "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_removing": "Error al eliminar {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_timeout": "{url:s} tardó demasiado en responder, abandono.", "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}", "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:", "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": "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." } From 6db09bf64e16b8fb31fb901d7e1a6deeaba843dd Mon Sep 17 00:00:00 2001 From: Flavio Cristoforetti Date: Wed, 11 Nov 2020 13:33:28 +0000 Subject: [PATCH 87/91] Translated using Weblate (Italian) Currently translated at 100.0% (57 of 57 strings) Translation: YunoHost/moulinette Translate-URL: https://translate.yunohost.org/projects/yunohost/moulinette/it/ --- locales/it.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/locales/it.json b/locales/it.json index e559de91..efe0bbdf 100644 --- a/locales/it.json +++ b/locales/it.json @@ -44,7 +44,7 @@ "error_writing_file": "Errore durante la scrittura del file {file: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}", - "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_timeout": "{url:s} ci ha messo troppo a rispondere, abbandonato.", "download_unknown_error": "Errore durante il download di dati da {url:s} : {error:s}", @@ -57,5 +57,5 @@ "corrupted_toml": "TOML corrotto da {ressource:s} (motivo: {errore:s})", "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, riprovo ad avviarlo..." + "ldap_server_is_down_restart_it": "Il servizio LDAP è terminato, provo a riavviarlo..." } From e2ed4b9fa070fa05e6cf6cf62c8038be7ac1c135 Mon Sep 17 00:00:00 2001 From: Thomas Blarre Date: Wed, 18 Nov 2020 06:19:59 +0000 Subject: [PATCH 88/91] Translated using Weblate (Portuguese) Currently translated at 100.0% (57 of 57 strings) Translation: YunoHost/moulinette Translate-URL: https://translate.yunohost.org/projects/yunohost/moulinette/pt/ --- locales/pt.json | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/locales/pt.json b/locales/pt.json index a7e70aea..e0081b9b 100644 --- a/locales/pt.json +++ b/locales/pt.json @@ -42,18 +42,20 @@ "error_writing_file": "Erro ao gravar arquivo {file: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}", - "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_timeout": "{url:s} demorou muito para responder, desistiu.", "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}", "command_unknown": "Comando '{command:s}' desconhecido ?", - "corrupted_json": "Json 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", + "corrupted_json": "JSON 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_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", - "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", - "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." } From 589e9cce9e03ce69e129ff9a746d4ba514d35af1 Mon Sep 17 00:00:00 2001 From: Weblate Date: Wed, 18 Nov 2020 19:55:24 +0000 Subject: [PATCH 89/91] Added translation using Weblate (Czech) --- locales/cs.json | 1 + 1 file changed, 1 insertion(+) create mode 100644 locales/cs.json diff --git a/locales/cs.json b/locales/cs.json new file mode 100644 index 00000000..0967ef42 --- /dev/null +++ b/locales/cs.json @@ -0,0 +1 @@ +{} From 7a0b0be0a95775f9b31037f739e496a8c3626e96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Milo=C5=A1=20Kroul=C3=ADk?= Date: Wed, 18 Nov 2020 19:56:48 +0000 Subject: [PATCH 90/91] Translated using Weblate (Czech) Currently translated at 1.7% (1 of 57 strings) Translation: YunoHost/moulinette Translate-URL: https://translate.yunohost.org/projects/yunohost/moulinette/cs/ --- locales/cs.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/locales/cs.json b/locales/cs.json index 0967ef42..c7272b61 100644 --- a/locales/cs.json +++ b/locales/cs.json @@ -1 +1,3 @@ -{} +{ + "password": "Heslo" +} From 4331cfa9d2efd222b0ee28368bdadda321d4cdaa Mon Sep 17 00:00:00 2001 From: Kay0u Date: Thu, 3 Dec 2020 16:33:32 +0100 Subject: [PATCH 91/91] Update changelog for 4.1.0 --- debian/changelog | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/debian/changelog b/debian/changelog index e23978dc..07789505 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,6 +1,15 @@ -moulinette (4.1) unstable; urgency=low +moulinette (4.1.0) testing; urgency=low - - Tmp bump version number for CI / tests + - [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 Thu, 03 Dec 2020 16:32:44 +0100 moulinette (4.0.3) stable; urgency=low