diff --git a/README.md b/README.md index bdeedcf5..dd61aafd 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ prototype interfaces for your application. Issues ------ -- [Please report issues on YunoHost bugtracker](https://dev.yunohost.org/projects/yunohost/issues) (no registration needed). +- [Please report issues on YunoHost bugtracker](https://github.com/YunoHost/issues). Overview -------- diff --git a/debian/changelog b/debian/changelog index 33897c61..5a98bd8c 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,95 @@ +moulinette (3.1.0) stable; urgency=low + + * Fix datetime output formats (#163) + * Add the missing Info: prefix for info messages + * Rework a bit the way we handle async outputs (#166) + * Don't crash the moulinette if we fail to format a string (#168) + + Thanks to all contributors : ljf, Bram, Aleks ! + + -- Alexandre Aubin Wed, 15 Aug 2018 21:44:00 +0000 + +moulinette (3.0.0) stable; urgency=low + + Merging with Jessie's branches + Releasing as stable + + -- Alexandre Aubin Sun, 17 Jun 2018 03:46:00 +0000 + +moulinette (3.0.0~beta1.1) testing; urgency=low + + Merging with Jessie's branches + + -- Alexandre Aubin Mon, 28 May 2018 02:55:00 +0000 + +moulinette (3.0.0~beta1) testing; urgency=low + + Beta release for Stretch + + -- Alexandre Aubin Thu, 03 May 2018 03:04:45 +0000 + +moulinette (2.7.14) stable; urgency=low + + * Improve French, Occitan, Portuguese, Arabic translations + * Release as stable + + -- Alexandre Aubin Sun, 17 Jun 2018 01:42:12 +0000 + +moulinette (2.7.13) testing; urgency=low + + * [i18n] Improve translations for Portugueuse, Occitan + * [enh] Add read_yaml util (#161) + + Contributors : Bram, by0ne, Quent-in + + -- Alexandre Aubin Mon, 28 May 2018 02:55:00 +0000 + +moulinette (2.7.12) stable; urgency=low + + * Bumping version number for stable release + + -- Alexandre Aubin Sun, 06 May 2018 16:57:18 +0000 + +moulinette (2.7.11) testing; urgency=low + + * [i18n] Improve translations for Arabic, Dutch, French, Occitan, Spanish + * [enh] Improve performances by lazy-loading some imports + * [enh] Log time needed to load a python module for an action + * [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 ! + + -- Alexandre Aubin Tue, 01 May 2018 23:33:59 +0000 + +moulinette (2.7.7) stable; urgency=low + + (Bumping version for stable release) + + -- Alexandre Aubin Thu, 18 Jan 2018 17:41:43 -0500 + +moulinette (2.7.6) testing; urgency=low + + * [fix] Indicate where those damn logs files are + * [i18n] Improve Spanish and French translations + + Thanks to all contributors (Bram, Jibec, David B.) ! <3 + + -- Alexandre Aubin Tue, 16 Jan 2018 17:12:19 -0500 + +moulinette (2.7.5) stable; urgency=low + + (Bumping version for stable release) + + -- Alexandre Aubin Sat, 02 Dec 2017 12:26:43 -0500 + +moulinette (2.7.3) testing; urgency=low + + * Optional expected status code for download_text/json (#153) + * Allow to give lock to multiple processes (#154) + + -- Alexandre Aubin Thu, 12 Oct 2017 16:09:27 -0400 + moulinette (2.7.2) stable; urgency=low * Revert hack for buildchain, found a proper solution diff --git a/doc/utils/filesystem.rst b/doc/utils/filesystem.rst index c58c3367..6ae30928 100644 --- a/doc/utils/filesystem.rst +++ b/doc/utils/filesystem.rst @@ -3,6 +3,7 @@ File system operation utils .. autofunction:: moulinette.utils.filesystem.read_file .. autofunction:: moulinette.utils.filesystem.read_json +.. autofunction:: moulinette.utils.filesystem.read_yaml .. autofunction:: moulinette.utils.filesystem.write_to_file .. autofunction:: moulinette.utils.filesystem.append_to_file .. autofunction:: moulinette.utils.filesystem.write_to_json diff --git a/locales/ar.json b/locales/ar.json new file mode 100644 index 00000000..f0ca8df8 --- /dev/null +++ b/locales/ar.json @@ -0,0 +1,54 @@ +{ + "argument_required": "المُعامِل '{argument}' مطلوب", + "authentication_profile_required": "المصادقة مع الملف الشخصي '{profile}' مطلوبة", + "authentication_required": "المصادقة مطلوبة", + "authentication_required_long": "المصادقة مطلوبة قبل القيام بهذا الإجراء", + "colon": "{}: ", + "confirm": "تأكيد {prompt}", + "deprecated_command": "'{prog} {command}' تم التخلي عنه و سوف تتم إزالته مستقبلا", + "deprecated_command_alias": "'{prog} {old}' تم التخلي عنه و سوف يتم إزالته مستقبلا، إستخدم '{prog} {new}' بدلا من ذلك", + "error": "خطأ :", + "error_see_log": "طرأ هناك خطأ. يرجى الإطلاع على السجلات للمزيد مِن التفاصيل على المسار /var/log/yunohost/.", + "file_exists": "إنّ الملف موجود من قبل : '{path}'", + "file_not_exist": "الملف غير موجود : '{path}'", + "folder_exists": "إنّ المجلد موجود من قبل : '{path}'", + "folder_not_exist": "المجلد غير موجود", + "instance_already_running": "هناك نسخة خادوم تشتغل مِن قبل", + "invalid_argument": "المُعامِل غير صالح '{argument}': {error}", + "invalid_password": "كلمة السر خاطئة", + "invalid_usage": "إستعمال غير صالح، إستخدم --help لعرض المساعدة", + "ldap_attribute_already_exists": "الخاصية '{attribute}' موجودة مسبقا و تحمل القيمة '{value}'", + "ldap_operation_error": "طرأ هناك خطأ أثناء عملية في LDAP", + "ldap_server_down": "لا يمكن الإتصال بخادم LDAP", + "logged_in": "مُتّصل", + "logged_out": "تم تسجيل خروجك", + "not_logged_in": "لم تقم بعدُ بتسجيل دخولك", + "operation_interrupted": "تم توقيف العملية", + "password": "كلمة السر", + "pattern_not_match": "لا يتطابق مع النموذج", + "permission_denied": "رُفض التصريح", + "root_required": "يتوجب عليك أن تكون مدير الجذر root للقيام بهذا الإجراء", + "server_already_running": "هناك خادم يشتغل على ذاك المنفذ", + "success": "تم بنجاح !", + "unable_authenticate": "تعذرت المصادقة", + "unable_retrieve_session": "تعذرت مواصلة الجلسة", + "unknown_group": "الفريق '{group}' مجهول", + "unknown_user": "المستخدم '{user}' مجهول", + "values_mismatch": "القيمتين غير متطابقتين", + "warning": "تحذير :", + "websocket_request_expected": "كان ينتظر طلبًا عبر الويب سوكت WebSocket", + "cannot_open_file": "ليس بالإمكان فتح الملف {file:s} (السبب : {error:s})", + "cannot_write_file": "لا يمكن الكتابة في الملف {file:s} (السبب : {error:s})", + "unknown_error_reading_file": "طرأ هناك خطأ ما أثناء عملية قراءة الملف {file:s}", + "corrupted_json": "قراءة json مُشوّهة مِن {ressource:s} (السبب : {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:s} (هل هذا الموقع موجود حقًا ؟)", + "download_ssl_error": "خطأ في الإتصال الآمن عبر الـ SSL أثناء محاولة الإتصال بـ {url:s}", + "download_timeout": "{url:s} استغرق مدة طويلة جدا للإستجابة، فتوقّف.", + "download_unknown_error": "خطأ أثناء عملية تنزيل البيانات مِن {url:s} : {error:s}", + "download_bad_status_code": "{url:s} أعاد رمز الحالة {code:s}", + "command_unknown": "الأمر '{command:s}' غير معروف ؟", + "corrupted_yaml": "قراءة مُشوّهة لنسق yaml مِن {ressource:s} (السبب : {error:s})" +} diff --git a/locales/en.json b/locales/en.json index a7156acd..e46a8751 100644 --- a/locales/en.json +++ b/locales/en.json @@ -8,11 +8,12 @@ "deprecated_command": "'{prog} {command}' is deprecated and will be removed in the future", "deprecated_command_alias": "'{prog} {old}' is deprecated and will be removed in the future, use '{prog} {new}' instead", "error": "Error:", - "error_see_log": "An error occurred. Please see the log for details.", + "error_see_log": "An error occurred. Please see the logs for details, they are located in /var/log/yunohost/.", "file_exists": "File already exists: '{path}'", "file_not_exist": "File does not exist: '{path}'", "folder_exists": "Folder already exists: '{path}'", "folder_not_exist": "Folder does not exist", + "info": "Info:", "instance_already_running": "An instance is already running", "invalid_argument": "Invalid argument '{argument}': {error}", "invalid_password": "Invalid password", @@ -42,6 +43,7 @@ "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}", "corrupted_json": "Corrupted json read from {ressource:s} (reason: {error:s})", + "corrupted_yaml": "Corrupted yaml 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}", diff --git a/locales/es.json b/locales/es.json index 52516dd2..36fd562b 100644 --- a/locales/es.json +++ b/locales/es.json @@ -8,12 +8,12 @@ "deprecated_command": "'{prog} {command}' está obsoleto y será eliminado en el futuro", "deprecated_command_alias": "'{prog} {old}' está obsoleto y se eliminará en el futuro, use '{prog} {new}' en su lugar", "error": "Error:", - "error_see_log": "Ha ocurrido un error. Consulte el registro para obtener más información.", + "error_see_log": "Ha ocurrido un error. Consulte el registro para obtener más información, localizado en /var/log/yunohost/.", "file_exists": "El archivo ya existe: '{path}'", "file_not_exist": "El archivo no existe: '{path}'", "folder_exists": "El directorio ya existe: '{path}'", "folder_not_exist": "El directorio no existe", - "instance_already_running": "El programa ya se está ejecutando", + "instance_already_running": "Una instancia ya se está ejecutando", "invalid_argument": "Argumento no válido '{argument}': {error}", "invalid_password": "Contraseña no válida", "invalid_usage": "Uso no válido, utilice --help para ver la ayuda", @@ -36,5 +36,18 @@ "unknown_user": "Usuario '{user}' desconocido", "values_mismatch": "Los valores no coinciden", "warning": "Advertencia:", - "websocket_request_expected": "Una petición de WebSocket prevista" + "websocket_request_expected": "Se esperaba una petición WebSocket", + "cannot_open_file": "No se pudo abrir el fichero{file:s} (motivo:{error:s})", + "cannot_write_file": "No se pudo escribir el fichero {file:s} (motivo: {error:s})", + "unknown_error_reading_file": "Error desconocido al intentar leer el fichero {file:s}", + "corrupted_json": "Json corrupto leido desde {ressource:s} (motivo: {error:s})", + "error_writing_file": "Error al escribir el fichero {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 web?)", + "download_ssl_error": "Error SSL al conectar con {url:s}", + "download_timeout": "{url:s} tardó demasiado en responder, me rindo.", + "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": "Comando '{command:s}' desconocido ?" } diff --git a/locales/fr.json b/locales/fr.json index a006ec42..911b9813 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -1,14 +1,14 @@ { "argument_required": "L'argument « {argument} » est requis", - "authentication_profile_required": "Authentification au profil « {profile} » requise", + "authentication_profile_required": "L’authentification au profil « {profile} » requise", "authentication_required": "Authentification requise", - "authentication_required_long": "L'authentification est requise pour exécuter cette action", + "authentication_required_long": "L’authentification est requise pour exécuter cette action", "colon": "{} : ", "confirm": "Confirmez : {prompt}", "deprecated_command": "« {prog} {command} » est déprécié et sera bientôt supprimé", "deprecated_command_alias": "« {prog} {old} » est déprécié et sera bientôt supprimé, utilisez « {prog} {new} » à la place", "error": "Erreur :", - "error_see_log": "Une erreur est survenue. Veuillez consulter les journaux pour plus de détails.", + "error_see_log": "Une erreur est survenue. Veuillez consulter les journaux pour plus de détails, ils sont situés en /var/log/yunohost/.", "file_exists": "Le fichier existe déjà : « {path} »", "file_not_exist": "Le fichier « {path} » n'existe pas", "folder_exists": "Le dossier existe déjà : « {path} »", @@ -27,7 +27,7 @@ "password": "Mot de passe", "pattern_not_match": "Ne correspond pas au motif", "permission_denied": "Permission refusée", - "root_required": "Vous devez avoir les droits super-utilisateur pour exécuter cette action", + "root_required": "Vous devez être super-utilisateur pour exécuter cette action", "server_already_running": "Un serveur est déjà en cours d'exécution sur ce port", "success": "Succès !", "unable_authenticate": "Impossible de vous authentifier", @@ -49,5 +49,6 @@ "download_timeout": "{url:s} a pris trop de temps pour répondre, abandon.", "download_unknown_error": "Erreur lors du téléchargement des données à partir de {url:s}:{error:s}", "download_bad_status_code": "{url:s} code de statut renvoyé {code:s}", - "command_unknown": "Commande {command:s} inconnue ?" + "command_unknown": "Commande '{command:s}' inconnue ?", + "corrupted_yaml": "YAML corrompu lu {ressource:s} depuis (cause : {error:s})" } diff --git a/locales/nl.json b/locales/nl.json index 22f7c766..54e72c97 100644 --- a/locales/nl.json +++ b/locales/nl.json @@ -1,21 +1,21 @@ { "argument_required": "Argument {argument} is vereist", "authentication_profile_required": "Authenticatie tot profiel '{profile}' is vereist", - "authentication_required": "Authenticatie vereist", - "authentication_required_long": "Authenticatie is vereist om deze actie uit te voeren", + "authentication_required": "Aanmelding vereist", + "authentication_required_long": "Aanmelding is vereist om deze actie uit te voeren", "colon": "{}: ", "confirm": "Bevestig {prompt}", "error": "Fout:", - "error_see_log": "Er is een fout opgetreden, zie logboek voor meer informatie.", - "file_exists": "Kan '{path}' niet aanmaken: Bestand bestaat al", + "error_see_log": "Er is een fout opgetreden, zie logboek voor meer informatie. Je kan deze vinden in /var/log/yunohost/.", + "file_exists": "Kan '{path}' niet aanmaken: bestand bestaat al", "file_not_exist": "Bestand bestaat niet: '{path}'", - "folder_exists": "kan map ‘{path}’ niet aanmaken: Bestand bestaat al", + "folder_exists": "Deze map bestaat al: '{path}'", "folder_not_exist": "Map bestaat niet", - "instance_already_running": "Er is al een instantie aan het draaien", - "invalid_argument": "Onjuist argument '{argument}': {error}", + "instance_already_running": "Er is al een instantie actief", + "invalid_argument": "Ongeldig argument '{argument}': {error}", "invalid_password": "Ongeldig wachtwoord", - "invalid_usage": "Geef 'pass --help' in voor meer informatie", - "ldap_attribute_already_exists": "Attribuut bestaat al: '{attribute}={value}'", + "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", @@ -26,7 +26,7 @@ "pattern_not_match": "Past niet in het patroon", "permission_denied": "Toegang geweigerd", "root_required": "Je moet root zijn om deze actie uit te voeren", - "server_already_running": "Er is al een server aktief op die poort", + "server_already_running": "Er is al een server actief op die poort", "success": "Succes!", "unable_authenticate": "Aanmelding niet mogelijk", "unable_retrieve_session": "Kan de sessie niet ophalen", @@ -34,7 +34,20 @@ "warning": "Waarschuwing:", "websocket_request_expected": "Verwachtte een WebSocket request", "deprecated_command": "'{prog} {command}' is verouderd en wordt binnenkort verwijderd", - "deprecated_command_alias": "'{prog} {old}' is verouderd en wordt binnenkort verwijderd, gebruik '{prog} {new}' in plaats daarvan", + "deprecated_command_alias": "'{prog} {old}' is verouderd en wordt binnenkort verwijderd, gebruik in de plaats '{prog} {new}'", "unknown_group": "Groep '{group}' is onbekend", - "unknown_user": "Gebruiker '{user}' is onbekend" + "unknown_user": "Gebruiker '{user}' is onbekend", + "cannot_open_file": "Niet mogelijk om bestand {file:s} te openen (reden: {error:s})", + "cannot_write_file": "Niet gelukt om bestand {file:s} te schrijven (reden: {error:s})", + "unknown_error_reading_file": "Ongekende fout tijdens het lezen van bestand {file:s}", + "corrupted_json": "Corrupte json gelezen van {ressource:s} (reden: {error:s})", + "error_writing_file": "Fout tijdens het schrijven van bestand {file:s}: {error:s}", + "error_removing": "Fout tijdens het verwijderen van {path:s}: {error:s}", + "error_changing_file_permissions": "Fout tijdens het veranderen van machtiging voor {path:s}: {error:s}", + "invalid_url": "Ongeldige URL {url:s} (bestaat deze website?)", + "download_ssl_error": "SSL fout gedurende verbinding met {url:s}", + "download_timeout": "{url:s} neemt te veel tijd om te antwoorden, we geven het op.", + "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 ?" } diff --git a/locales/oc.json b/locales/oc.json new file mode 100644 index 00000000..7ec0cfb6 --- /dev/null +++ b/locales/oc.json @@ -0,0 +1,54 @@ +{ + "argument_required": "L’argument {argument} es requesit", + "authentication_profile_required": "L’identificacion del perfil {profile} es requesida", + "authentication_required": "Autentificacion requesida", + "authentication_required_long": "Una autentificacion es requesida per acomplir aquesta accion", + "logged_in": "Connectat", + "logged_out": "Desconnectat", + "password": "Senhal", + "colon": "{}: ", + "confirm": "Confirmatz : {prompt}", + "deprecated_command": "« {prog} {command} » es despreciat e serà lèu suprimit", + "deprecated_command_alias": "« {prog} {old} » es despreciat e serà lèu suprimit, utilizatz « {prog} {new} » allòc", + "error": "Error :", + "error_see_log": "Una error s’es producha. Mercés de consultar los jornals per mai detalhs, son plaçats dins /var/log/yunohost/.", + "file_exists": "Lo fichièr existís ja : « {path} »", + "file_not_exist": "Lo fichièr « {path} » existís pas", + "folder_exists": "Lo repertòri existís ja : « {path} »", + "folder_not_exist": "Lo repertòri existís pas", + "instance_already_running": "Una instància es ja en execucion", + "invalid_argument": "Argument « {argument} » incorrècte : {error}", + "invalid_password": "Senhal incorrècte", + "ldap_server_down": "Impossible d’aténher lo servidor LDAP", + "not_logged_in": "Cap de session començada", + "pattern_not_match": "Correspond pas al patron", + "permission_denied": "Permission refusada", + "root_required": "Cal èsser root per realizar aquesta accion", + "unable_retrieve_session": "Impossible de recuperar la session", + "unknown_group": "Grop « {group} » desconegut", + "unknown_user": "Utilizaire « {user} » desconegut", + "values_mismatch": "Las valors correspondon pas", + "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 !", + "unable_authenticate": "Impossible de vos autentificar", + "websocket_request_expected": "Una requèsta WebSocket èra esperada", + "cannot_open_file": "Impossible de dobrir lo fichièr {file:s} (rason : {error:s})", + "cannot_write_file": "Escritura impossibla del fichièr {file:s} (rason : {error:s})", + "unknown_error_reading_file": "Error desconeguda en ensajar de legir lo fichièr {file:s}", + "error_writing_file": "Error en escriure lo fichièr {file:s} : {error:s}", + "error_removing": "Error en suprimir {path:s} : {error:s}", + "error_changing_file_permissions": "Error en modificar las permissions per {path:s} : {error:s}", + "invalid_url": "Url invalida {url:s} (existís aqueste site ?)", + "download_ssl_error": "Error SSL en se connectant a {url:s}", + "download_timeout": "{url:s} a trigat per respondre, avèm quitat d’esperar.", + "download_unknown_error": "Error en telecargar de donadas de {url:s} : {error:s}", + "download_bad_status_code": "{url:s} tòrna lo còdi d’estat {code:s}", + "command_unknown": "Comanda {command:s} desconeguda ?", + "corrupted_json": "Fichièr Json corromput legit de {ressource:s} (rason : {error:s})", + "corrupted_yaml": "Fichièr YAML corromput legit de {ressource:s} (rason : {error:s})" +} diff --git a/locales/pt.json b/locales/pt.json index 896da217..cfc73e1e 100644 --- a/locales/pt.json +++ b/locales/pt.json @@ -34,7 +34,21 @@ "websocket_request_expected": "Esperado um pedido a WebSocket", "deprecated_command": "'{prog} {command}' está obsoleto e será removido no futuro", "deprecated_command_alias": "'{prog} {old}' está obsoleto e será removido no futuro, em vez disso, usa '{prog} {new}'", - "error_see_log": "Um erro ocorreu. Por favor, veja mais detalhes no log.", + "error_see_log": "Ocorreu um erro . Por favor, veja os logs para maiores detalhes, eles estão localizados em /var/log/yunohost/.", "unknown_group": "Grupo '{group}' desconhecido", - "unknown_user": "Nome de utilizador '{user}' desconhecido" + "unknown_user": "Nome de utilizador '{user}' desconhecido", + "cannot_open_file": "Não foi possível abrir o arquivo {file:s} (reason: {error:s})", + "cannot_write_file": "Não foi possível abrir o arquivo {file:s} (reason: {error:s})", + "unknown_error_reading_file": "Erro desconhecido ao tentar ler o arquivo {file:s}", + "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 ?)", + "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})" } diff --git a/moulinette/actionsmap.py b/moulinette/actionsmap.py index 9e1468f1..807eab14 100644 --- a/moulinette/actionsmap.py +++ b/moulinette/actionsmap.py @@ -391,7 +391,7 @@ class ActionsMap(object): with open(actionsmap_pkl) as f: actionsmaps[n] = pickle.load(f) # TODO: Switch to python3 and catch proper exception - except IOError: + except (IOError, EOFError): self.use_cache = False actionsmaps = self.generate_cache(namespaces) elif use_cache: # cached file doesn't exists @@ -469,10 +469,13 @@ class ActionsMap(object): # Lock the moulinette for the namespace with MoulinetteLock(namespace, timeout): + start = time() try: mod = __import__('%s.%s' % (namespace, category), globals=globals(), level=0, fromlist=[func_name]) + logger.debug('loading python module %s took %.3fs', + '%s.%s' % (namespace, category), time() - start) func = getattr(mod, func_name) except (AttributeError, ImportError): logger.exception("unable to load function %s.%s", @@ -495,7 +498,7 @@ class ActionsMap(object): return func(**arguments) finally: stop = time() - logger.debug('action [%s] ended after %.3fs', + logger.debug('action [%s] executed in %.3fs', log_id, stop - start) @staticmethod diff --git a/moulinette/authenticators/ldap.py b/moulinette/authenticators/ldap.py index ea874605..53cd0899 100644 --- a/moulinette/authenticators/ldap.py +++ b/moulinette/authenticators/ldap.py @@ -62,7 +62,8 @@ class Authenticator(BaseAuthenticator): try: # Retrieve identity who = self.con.whoami_s() - except: + except Exception as e: + logger.warning("Error during ldap authentication process: %s", e) return False else: if who[3:] == self.userdn: @@ -131,9 +132,9 @@ class Authenticator(BaseAuthenticator): try: result = self.con.search_s(base, ldap.SCOPE_SUBTREE, filter, attrs) - except: + except Exception as e: logger.exception("error during LDAP search operation with: base='%s', " - "filter='%s', attrs=%s", base, filter, attrs) + "filter='%s', attrs=%s and exception %s", base, filter, attrs, e) raise MoulinetteError(169, m18n.g('ldap_operation_error')) result_list = [] @@ -162,9 +163,9 @@ class Authenticator(BaseAuthenticator): try: self.con.add_s(dn, ldif) - except: + except Exception as e: logger.exception("error during LDAP add operation with: rdn='%s', " - "attr_dict=%s", rdn, attr_dict) + "attr_dict=%s and exception %s", rdn, attr_dict, e) raise MoulinetteError(169, m18n.g('ldap_operation_error')) else: return True @@ -183,8 +184,8 @@ class Authenticator(BaseAuthenticator): dn = rdn + ',' + self.basedn try: self.con.delete_s(dn) - except: - logger.exception("error during LDAP delete operation with: rdn='%s'", rdn) + except Exception as e: + logger.exception("error during LDAP delete operation with: rdn='%s' and exception %s", rdn, e) raise MoulinetteError(169, m18n.g('ldap_operation_error')) else: return True @@ -212,9 +213,10 @@ class Authenticator(BaseAuthenticator): dn = new_rdn + ',' + self.basedn self.con.modify_ext_s(dn, ldif) - except: + except Exception as e: logger.exception("error during LDAP update operation with: rdn='%s', " - "attr_dict=%s, new_rdn=%s", rdn, attr_dict, new_rdn) + "attr_dict=%s, new_rdn=%s and exception: %s", rdn, attr_dict, + new_rdn, e) raise MoulinetteError(169, m18n.g('ldap_operation_error')) else: return True diff --git a/moulinette/core.py b/moulinette/core.py index 1407f76d..c6e367f7 100644 --- a/moulinette/core.py +++ b/moulinette/core.py @@ -88,14 +88,23 @@ class Translator(object): - key -- The key to translate """ + failed_to_format = False if key in self._translations.get(self.locale, {}): - return self._translations[self.locale][key].encode('utf-8').format(*args, **kwargs) + try: + return self._translations[self.locale][key].encode('utf-8').format(*args, **kwargs) + except KeyError as e: + logger.exception("Failed to format translated string '%s' with error: %s" % (key, e)) + failed_to_format = True - if self.default_locale != self.locale and key in self._translations.get(self.default_locale, {}): + if failed_to_format or (self.default_locale != self.locale and key in self._translations.get(self.default_locale, {})): logger.info("untranslated key '%s' for locale '%s'", key, self.locale) - return self._translations[self.default_locale][key].encode('utf-8').format(*args, **kwargs) + try: + return self._translations[self.default_locale][key].encode('utf-8').format(*args, **kwargs) + except KeyError as e: + logger.exception("Failed to format translatable string '%s' with error: %s" % (key, e)) + return self._translations[self.locale][key].encode('utf-8') logger.exception("unable to retrieve key '%s' for default locale '%s'", key, self.default_locale) @@ -500,7 +509,7 @@ class MoulinetteLock(object): lock_pids = f.read().strip().split('\n') # Make sure to convert those pids to integers - lock_pids = [ int(pid) for pid in lock_pids ] + lock_pids = [int(pid) for pid in lock_pids if pid.strip() != ''] return lock_pids diff --git a/moulinette/interfaces/api.py b/moulinette/interfaces/api.py index 1c5e3d67..c102d9af 100644 --- a/moulinette/interfaces/api.py +++ b/moulinette/interfaces/api.py @@ -438,7 +438,7 @@ class _ActionsMapPlugin(object): try: s_secret = self.secrets[s_id] s_hash = request.get_cookie('session.hashes', - secret=s_secret)[authenticator.name] + secret=s_secret, default={})[authenticator.name] except KeyError: if authenticator.name == 'default': msg = m18n.g('authentication_required') diff --git a/moulinette/utils/filesystem.py b/moulinette/utils/filesystem.py index 1740a7a6..07da4bf7 100644 --- a/moulinette/utils/filesystem.py +++ b/moulinette/utils/filesystem.py @@ -1,4 +1,5 @@ import os +import yaml import errno import shutil import json @@ -64,6 +65,28 @@ def read_json(file_path): return loaded_json +def read_yaml(file_path): + """ + Safely read a yaml file + + Keyword argument: + file_path -- Path to the yaml file + """ + + # Read file + file_content = read_file(file_path) + + # Try to load yaml to check if it's syntaxically correct + try: + loaded_yaml = yaml.safe_load(file_content) + except ValueError as e: + raise MoulinetteError(errno.EINVAL, + m18n.g('corrupted_yaml', + ressource=file_path, error=str(e))) + + return loaded_yaml + + def write_to_file(file_path, data, file_mode="w"): """ Write a single string or a list of string to a text file. diff --git a/moulinette/utils/network.py b/moulinette/utils/network.py index c083dea6..27e753e3 100644 --- a/moulinette/utils/network.py +++ b/moulinette/utils/network.py @@ -1,5 +1,4 @@ import errno -import requests import json from moulinette import m18n @@ -17,6 +16,7 @@ def download_text(url, timeout=30, expected_status_code=200): expected_status_code -- Status code expected from the request. Can be None to ignore the status code. """ + import requests # lazy loading this module for performance reasons # Assumptions assert isinstance(url, str) diff --git a/moulinette/utils/process.py b/moulinette/utils/process.py index 6e10d4dd..299b96d5 100644 --- a/moulinette/utils/process.py +++ b/moulinette/utils/process.py @@ -95,22 +95,33 @@ def call_async_output(args, callback, **kwargs): if stdinfo: stdinfo_reader, stdinfo_consum = async_file_reading(stdinfo_f, callback[2]) - while not stdout_reader.eof() or not stderr_reader.eof(): + while not stdout_reader.eof() and not stderr_reader.eof(): + while not stdout_consum.empty() or not stderr_consum.empty(): + # alternate between the 2 consumers to avoid desynchronisation + # this way is not 100% perfect but should do it + stdout_consum.process_next_line() + stderr_consum.process_next_line() + stdinfo_consum.process_next_line() time.sleep(.1) stderr_reader.join() - stderr_consum.join() + # clear the queues + stdout_consum.process_current_queue() + stderr_consum.process_current_queue() + stdinfo_consum.process_current_queue() else: while not stdout_reader.eof(): + stdout_consum.process_current_queue() time.sleep(.1) stdout_reader.join() - stdout_consum.join() + # clear the queue + stdout_consum.process_current_queue() if stdinfo: # Remove the stdinfo pipe os.remove(stdinfo) os.rmdir(os.path.dirname(stdinfo)) stdinfo_reader.join() - stdinfo_consum.join() + stdinfo_consum.process_current_queue() # on slow hardware, in very edgy situations it is possible that the process # isn't finished just after having closed stdout and stderr, so we wait a diff --git a/moulinette/utils/serialize.py b/moulinette/utils/serialize.py index bc0e6d1d..800cf1b0 100644 --- a/moulinette/utils/serialize.py +++ b/moulinette/utils/serialize.py @@ -1,5 +1,6 @@ import logging from json.encoder import JSONEncoder +import datetime logger = logging.getLogger('moulinette.utils.serialize') @@ -24,6 +25,10 @@ class JSONExtendedEncoder(JSONEncoder): hasattr(o, '__iter__') and hasattr(o, 'next')): return list(o) + # Display the date in its iso format ISO-8601 Internet Profile (RFC 3339) + if isinstance(o, datetime.datetime) or isinstance(o, datetime.date): + return o.isoformat() + # Return the repr for object that json can't encode logger.warning('cannot properly encode in JSON the object %s, ' 'returned repr is: %r', type(o), o) diff --git a/moulinette/utils/stream.py b/moulinette/utils/stream.py index 7b16897e..b11f7490 100644 --- a/moulinette/utils/stream.py +++ b/moulinette/utils/stream.py @@ -73,14 +73,29 @@ class AsynchronousFileReader(Process): Process.join(self, timeout) -def consume_queue(queue, callback): - """Consume the queue and give content to the callback.""" - while True: - line = queue.get() - if line: - if line == StopIteration: - break - callback(line) +class Consummer(object): + def __init__(self, queue, callback): + self.queue = queue + self.callback = callback + + def empty(self): + return self.queue.empty() + + def process_next_line(self): + if not self.empty(): + line = self.queue.get() + if line: + if line == StopIteration: + return + self.callback(line) + + def process_current_queue(self): + while not self.empty(): + line = self.queue.get() + if line: + if line == StopIteration: + break + self.callback(line) def async_file_reading(fd, callback): @@ -88,6 +103,5 @@ def async_file_reading(fd, callback): queue = SimpleQueue() reader = AsynchronousFileReader(fd, queue) reader.start() - consummer = Process(target=consume_queue, args=(queue, callback)) - consummer.start() + consummer = Consummer(queue, callback) return (reader, consummer)