From 311ecc7f3a07f25e66ba7dc99d0bd8875632f41a Mon Sep 17 00:00:00 2001 From: ggil Date: Tue, 25 Mar 2025 14:12:03 +0100 Subject: [PATCH] refs #1733 - Add 'Rename Image' functionality --- CHANGELOG.md | 8 +++ README.md | 29 ++++++++- api/README.md | 29 ++++++++- api/repo_api.py | 74 ++++++++++++++++++++- api/swagger.yaml | 83 ++++++++++++++++++++++++ api/test_repo_api.py | 56 ++++++++++++++++ bin/renameImage.py | 149 +++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 423 insertions(+), 5 deletions(-) create mode 100644 bin/renameImage.py diff --git a/CHANGELOG.md b/CHANGELOG.md index deb6985..0f68d89 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog + +## [0.8.0] - 2025-03-25 + +### Added + +- Rename Image functionality + ## [0.7.3] - 2025-03-19 + ### Changed - Fix permissions problem and authorized_keys file in debian package diff --git a/README.md b/README.md index 18228e9..4858941 100644 --- a/README.md +++ b/README.md @@ -74,8 +74,9 @@ El presente documento detalla los endpoints de la API, con sus respectivos pará 18. [Hacer Backup de una Imagen](#hacer-backup-de-una-imagen) - `PUT /ogrepository/v1/repo/images` 19. [Convertir Imagen Virtual a Imagen OpenGnsys](#convertir-imagen-virtual-a-imagen-opengnsys) - `POST /ogrepository/v1/images/virtual` 20. [Convertir Imagen OpenGnsys a Imagen Virtual](#convertir-imagen-opengnsys-a-imagen-virtual) - `PUT /ogrepository/v1/images/virtual` -21. [Crear archivos auxiliares](#crear-archivos-auxiliares) - `POST /ogrepository/v1/images/torrentsum` -22. [Enviar paquete Wake On Lan](#enviar-paquete-wake-on-lan) - `POST /ogrepository/v1/wol` +21. [Renombrar una Imagen](#renombrar-una-imagen) - `POST /ogrepository/v1/images/rename` +22. [Crear archivos auxiliares](#crear-archivos-auxiliares) - `POST /ogrepository/v1/images/torrentsum` +23. [Enviar paquete Wake On Lan](#enviar-paquete-wake-on-lan) - `POST /ogrepository/v1/wol` --- ### Obtener Información de Estado de ogRepository @@ -677,6 +678,30 @@ curl -X PUT -H "Authorization: $API_KEY" -H "Content-Type: application/json" -d - **Código 400 Bad Request:** No se ha encontrado la imagen especificada. - **Código 200 OK:** La imagen se está convirtiendo a virtual. +--- +### Renombrar una Imagen + +Se renombrará la imagen especificada como primer parámetro (y todos sus archivos asociados), asignando el nombre especificado como segundo parámetro. +Se puede hacer con el script "**renameImage.py**", que debe ser llamado por el endpoint. +**NOTA**: El script requiere que se le pase el nombre de la imagen (sin extensión) como primer parámetro, y el nuevo nombre a asignar como segundo parámetro. El primer parámetro se obtiene en la API, a partir del ID de la imagen (que corresponde al contenido del archivo "full.sum"), pero el nuevo nombre a asignar debe enviarse en el JSON. + +**URL:** `/ogrepository/v1/images/rename` +**Método HTTP:** POST + +**Cuerpo de la Solicitud (JSON):** +- **ID_img**: Identificador de la imagen (correspondiente al contenido del archivo "full.sum" asociado). +- **image_new_name**: Nuevo nombre a asignar a la imagen. + +**Ejemplo de Solicitud:** + +```bash +curl -X POST -H "Authorization: $API_KEY" -H "Content-Type: application/json" -d '{"ID_img":"22735b9070e4a8043371b8c6ae52b90d", "image_new_name":"Ubuntu_BKP"}' http://example.com/ogrepository/v1/images/rename +``` +**Respuestas:** +- **Código 500 Internal Server Error:** Ocurrió un error al renombrar la imagen. +- **Código 400 Bad Request:** No se ha encontrado la imagen especificada. +- **Código 200 OK:** La imagen se ha renombrado exitosamente. + --- ### Crear archivos auxiliares diff --git a/api/README.md b/api/README.md index df56ffd..f244daa 100644 --- a/api/README.md +++ b/api/README.md @@ -28,8 +28,9 @@ El presente documento detalla los endpoints de la API, con sus respectivos pará 18. [Hacer Backup de una Imagen](#hacer-backup-de-una-imagen) - `PUT /ogrepository/v1/repo/images` 19. [Convertir Imagen Virtual a Imagen OpenGnsys](#convertir-imagen-virtual-a-imagen-opengnsys) - `POST /ogrepository/v1/images/virtual` 20. [Convertir Imagen OpenGnsys a Imagen Virtual](#convertir-imagen-opengnsys-a-imagen-virtual) - `PUT /ogrepository/v1/images/virtual` -21. [Crear archivos auxiliares](#crear-archivos-auxiliares) - `POST /ogrepository/v1/images/torrentsum` -22. [Enviar paquete Wake On Lan](#enviar-paquete-wake-on-lan) - `POST /ogrepository/v1/wol` +21. [Renombrar una Imagen](#renombrar-una-imagen) - `POST /ogrepository/v1/images/rename` +22. [Crear archivos auxiliares](#crear-archivos-auxiliares) - `POST /ogrepository/v1/images/torrentsum` +23. [Enviar paquete Wake On Lan](#enviar-paquete-wake-on-lan) - `POST /ogrepository/v1/wol` --- ### Obtener Información de Estado de ogRepository @@ -631,6 +632,30 @@ curl -X PUT -H "Authorization: $API_KEY" -H "Content-Type: application/json" -d - **Código 400 Bad Request:** No se ha encontrado la imagen especificada. - **Código 200 OK:** La imagen se está convirtiendo a virtual. +--- +### Renombrar una Imagen + +Se renombrará la imagen especificada como primer parámetro (y todos sus archivos asociados), asignando el nombre especificado como segundo parámetro. +Se puede hacer con el script "**renameImage.py**", que debe ser llamado por el endpoint. +**NOTA**: El script requiere que se le pase el nombre de la imagen (sin extensión) como primer parámetro, y el nuevo nombre a asignar como segundo parámetro. El primer parámetro se obtiene en la API, a partir del ID de la imagen (que corresponde al contenido del archivo "full.sum"), pero el nuevo nombre a asignar debe enviarse en el JSON. + +**URL:** `/ogrepository/v1/images/rename` +**Método HTTP:** POST + +**Cuerpo de la Solicitud (JSON):** +- **ID_img**: Identificador de la imagen (correspondiente al contenido del archivo "full.sum" asociado). +- **image_new_name**: Nuevo nombre a asignar a la imagen. + +**Ejemplo de Solicitud:** + +```bash +curl -X POST -H "Authorization: $API_KEY" -H "Content-Type: application/json" -d '{"ID_img":"22735b9070e4a8043371b8c6ae52b90d", "image_new_name":"Ubuntu_BKP"}' http://example.com/ogrepository/v1/images/rename +``` +**Respuestas:** +- **Código 500 Internal Server Error:** Ocurrió un error al renombrar la imagen. +- **Código 400 Bad Request:** No se ha encontrado la imagen especificada. +- **Código 200 OK:** La imagen se ha renombrado exitosamente. + --- ### Crear archivos auxiliares diff --git a/api/repo_api.py b/api/repo_api.py index 54f5ed8..a8772ea 100644 --- a/api/repo_api.py +++ b/api/repo_api.py @@ -2019,7 +2019,7 @@ def convert_image_to_virtual(): """ journal.send("Running endpoint 'Convertir imagen OpenGnsys a imagen virtual'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - # Almacenamos los parámetros enviados en el JSON,: + # Almacenamos los parámetros enviados en el JSON: json_data = json.loads(request.data) image_id = json_data.get("ID_img") vm_extension = json_data.get("vm_extension").lower().lstrip('.') @@ -2112,6 +2112,78 @@ def convert_image_to_virtual(): }), 500 +# --------------------------------------------------------- + + +# 22 - Endpoint "Renombrar una Imagen" (SINCRONO): +@app.route("/ogrepository/v1/images/rename", methods=['PUT']) +def rename_image(): + """ Este endpoint renombra la imagen especificada como primer parámetro (y todos sus archivos asociados), asignando el nombre especificado como segundo parámetro. + Para ello, ejecuta el script "renameImage.py", con el nombre original de la imagen como primer parámetro, y el nuevo nombre a asignar como segundo parámetro. + """ + journal.send("Running endpoint 'Renombrar una Imagen'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + + # Almacenamos los parámetros enviados en el JSON: + json_data = json.loads(request.data) + image_id = json_data.get("ID_img") + image_new_name = json_data.get("image_new_name").rstrip('.img') + + # Obtenemos el nombre y la extensión de la imagen: + param_dict = get_image_params(image_id, "repo") + + # Evaluamos los parámetros obtenidos, para construir la llamada al script, o para devolver un error si no se ha encontrado la imagen: + if param_dict: + image_original_name = f"{param_dict['name']}" + cmd = ['python3', f"{script_path}/renameImage.py", image_original_name, image_new_name] + else: + journal.send("Image not found", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + journal.send("{'component':'ogRepo', 'severity':'WARNING', 'http_code':'400', 'operation':'Run endpoint rename_image', 'desc':'Warning: Image not found'}", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api") + return jsonify({ + "success": False, + "error": "Image not found" + }), 400 + + # Comprobamos si ya existe una imagen con el mismo nombre que se quiere asignar, llamando a la función "check_file_exists": + image_newname_exists = check_file_exists(f"{repo_path}{image_new_name}.img") + + # Si existe una imagen con el mismo nombre que se quiere asignar, devolvemos un error: + if image_newname_exists == True: + journal.send("There is an image with the name to assign", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + journal.send("{'component':'ogRepo', 'severity':'WARNING', 'http_code':'400', 'operation':'Run endpoint convert_image_to_virtual', 'desc':'Warning: There is an image with the name to assign'}", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api") + return jsonify({ + "success": False, + "exception": "There is an image with the name to assign" + }), 400 + + try: + journal.send("Running script 'renameImage.py'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + # Ejecutamos el script "renameImage.py" (con los parámetros almacenados), y almacenamos el resultado: + result = subprocess.run(cmd, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding='UTF8') + + # Evaluamos el resultado de la ejecución, y devolvemos la respuesta: + if result.returncode == 0: + journal.send("Script 'renameImage.py' result OK (ReturnCode: 0)", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + journal.send("{'component':'ogRepo', 'severity':'INFO', 'http_code':'200', 'operation':'Run script renameImage.py', 'desc':'Result OK (ReturnCode: 0)'}", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api") + return jsonify({ + "success": True, + "output": "Image renamed successfully" + }), 200 + else: + journal.send(f"Script 'renameImage.py' result KO (Error: {result.stderr})", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + journal.send(f"{{'component':'ogRepo', 'severity':'ERROR', 'http_code':'500', 'operation':'Run script renameImage.py', 'desc':'Result KO (Error: {result.stderr})'}}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api") + return jsonify({ + "success": False, + "error": result.stderr + }), 500 + except Exception as error_description: + journal.send(f"Script 'renameImage.py' result KO (Exception: {str(error_description)})", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + journal.send(f"{{'component':'ogRepo', 'severity':'ERROR', 'http_code':'500', 'operation':'Run script renameImage.py', 'desc':'Result KO (Exception: {str(error_description)})'}}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api") + return jsonify({ + "success": False, + "exception": str(error_description) + }), 500 + + # -------------------------------------------------------------------------------------------- diff --git a/api/swagger.yaml b/api/swagger.yaml index b239ab2..a7a8ebc 100644 --- a/api/swagger.yaml +++ b/api/swagger.yaml @@ -1876,4 +1876,87 @@ paths: type: string example: "(Exception description)" +# ----------------------------------------------------------------------------------------------------------- + + /ogrepository/v1/images/rename: + put: + summary: "Renombrar una Imagen" + description: | + Este endpoint renombra la imagen especificada como primer parámetro (y todos sus archivos asociados), asignando el nombre especificado como segundo parámetro. + Utiliza el script "**renameImage.py**", que recibe como parámetros el nombre original y el nuevo nombre a asignar. + tags: + - "Varios" + parameters: + - name: JSON + in: body + required: true + description: | + * **ID_img** - Identificador de la imagen, correspondiente al contenido del archivo 'full.sum' + * **image_new_name** - Nuevo nombre a asignar a la imagen + schema: + type: object + properties: + ID_img: + type: string + example: "22735b9070e4a8043371b8c6ae52b90d" + image_new_name: + type: string + example: "Ubuntu_BKP" + responses: + "200": + description: "La imagen se ha renombrado exitosamente." + schema: + type: object + properties: + success: + type: boolean + example: true + output: + type: string + example: "Image renamed successfully" + "400 (Image not found)": + description: "No se ha encontrado la imagen." + schema: + type: object + properties: + success: + type: boolean + example: false + exception: + type: string + example: "Image not found" + "400 (Name incorrect)": + description: "Ya existe una imagen con el nombre a asignar." + schema: + type: object + properties: + success: + type: boolean + example: false + exception: + type: string + example: "There is an image with the name to assign" + "500 (Error)": + description: "Error al renombrar la imagen." + schema: + type: object + properties: + success: + type: boolean + example: false + error: + type: string + example: "(Error description)" + "500 (Exception)": + description: "Excepción inesperada al renombrar la imagen." + schema: + type: object + properties: + success: + type: boolean + example: false + exception: + type: string + example: "(Exception description)" + # ----------------------------------------------------------------------------------------------------------- diff --git a/api/test_repo_api.py b/api/test_repo_api.py index 64063b5..a817cf4 100644 --- a/api/test_repo_api.py +++ b/api/test_repo_api.py @@ -1109,6 +1109,62 @@ class RepoApiTestCase(unittest.TestCase): self.assertIn('Error al cancelar las transmisiones P2P', response.data.decode()) + # ------------------------------------------------------------------- Tests "Renombrar una Imagen" + + + @patch('repo_api.get_image_params') + @patch('repo_api.subprocess.run') + def test_rename_image(self, mock_subprocess, mock_get_image_params): + """ Método de prueba del endpoint "Renombrar una Imagen". + """ + print("Testing endpoint 'Renombrar una Imagen'...") + mock_get_image_params.return_value = {'name': 'test4unittest', 'extension': 'img'} + mock_subprocess.return_value = MagicMock(returncode=0) + response = self.app.put('/ogrepository/v1/images/rename', data=json.dumps({"ID_img":"test_image_id", "image_new_name":"new_name"}), content_type='application/json') + self.assertEqual(response.status_code, 200) + self.assertIn('Image renamed successfully', json.loads(response.data)['output']) + + + @patch('repo_api.get_image_params') + @patch('repo_api.subprocess.run') + def test_rename_image_error500(self, mock_subprocess, mock_get_image_params): + """ Método de prueba del endpoint "Renombrar una Imagen" + (en caso de error "500"). + """ + print("Testing endpoint 'Renombrar una Imagen' (error 500)...") + mock_get_image_params.return_value = {'name': 'test4unittest', 'extension': 'img'} + mock_subprocess.side_effect = Exception("Error al renombrar la imagen") + response = self.app.put('/ogrepository/v1/images/rename', data=json.dumps({"ID_img":"test_image_id", "image_new_name":"new_name"}), content_type='application/json') + self.assertEqual(response.status_code, 500) + self.assertIn('Error al renombrar la imagen', response.data.decode()) + + + @patch('repo_api.get_image_params') + def test_rename_image_error400_no_image(self, mock_get_image_params): + """ Método de prueba del endpoint "Renombrar una Imagen" + (en caso de error "400", por imagen inexistente). + """ + print("Testing endpoint 'Renombrar una Imagen' (error 400, no image)...") + mock_get_image_params.return_value = None + response = self.app.put('/ogrepository/v1/images/rename', data=json.dumps({"ID_img":"test_image_id", "image_new_name":"new_name"}), content_type='application/json') + self.assertEqual(response.status_code, 400) + self.assertIn('Image not found', json.loads(response.data)['error']) + + + @patch('repo_api.check_file_exists') + @patch('repo_api.get_image_params') + def test_rename_image_error400_name_incorrect(self, mock_get_image_params, mock_check_file_exists): + """ Método de prueba del endpoint "Renombrar una Imagen" + (en caso de error "400", por nombre incorrecto). + """ + print("Testing endpoint 'Renombrar una Imagen' (error 400, name incorrect)...") + mock_get_image_params.return_value = {'name': 'test4unittest', 'extension': 'img'} + mock_check_file_exists.return_value = True + response = self.app.put('/ogrepository/v1/images/rename', data=json.dumps({"ID_img":"test_image_id", "image_new_name":"new_name"}), content_type='application/json') + self.assertEqual(response.status_code, 400) + self.assertIn('There is an image with the name to assign', json.loads(response.data)['exception']) + + # -------------------------------------------------------------------------------------------- diff --git a/bin/renameImage.py b/bin/renameImage.py new file mode 100644 index 0000000..784b1cc --- /dev/null +++ b/bin/renameImage.py @@ -0,0 +1,149 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +""" +Este script renombra la imagen especificada como primer parámetro (y todos sus archivos asociados), asignando el nombre especificado como segundo parámetro. + +Una vez renombrados los archivos, llama al script "updateRepoInfo.py", para actualizar la información del repositorio. + + Parámetros +------------ +sys.argv[1] - Nombre de la imagen a renombrar (sin extensión y sin ruta). + - Ejemplo1: Ubuntu24 + - Ejemplo2: Windows10 + +sys.argv[2] - Nuevo nombre a asignar a la imagen (sin extensión y sin ruta). + - Ejemplo1: Ubuntu24_BKP + - Ejemplo2: Windows10_BKP + + Sintaxis +---------- +./renameImage.py image_to_rename image_new_name + + Ejemplos + --------- +./renameImage.py Ubuntu24 Ubuntu24_BKP +./renameImage.py Windows10 Windows10_BKP +""" + +# -------------------------------------------------------------------------------------------- +# IMPORTS +# -------------------------------------------------------------------------------------------- + +import os +import sys +import subprocess +from systemd import journal + + +# -------------------------------------------------------------------------------------------- +# VARIABLES +# -------------------------------------------------------------------------------------------- + +script_name = os.path.basename(__file__) +repo_path = '/opt/opengnsys/ogrepository/images/' # No borrar la barra final +update_repo_script = '/opt/opengnsys/ogrepository/bin/updateRepoInfo.py' + + +# -------------------------------------------------------------------------------------------- +# FUNCTIONS +# -------------------------------------------------------------------------------------------- + + +def show_help(): + """ Imprime la ayuda, cuando se ejecuta el script con el parámetro "help". + """ + help_text = f""" + Sintaxis: {script_name} image_to_rename image_new_name + Ejemplo1: {script_name} Ubuntu24 Ubuntu24_BKP + Ejemplo2: {script_name} Windows10 Windows10_BKP + """ + print(help_text) + + + +def check_params(): + """ Comprueba que se haya enviado la cantidad correcta de parámetros, y en el formato correcto. + Si no es así, muestra un mensaje de error, y sale del script. + LLama a la función "show_help" cuando se ejecuta el script con el parámetro "help". + """ + # Si se ejecuta el script con el parámetro "help", se muestra la ayuda, y se sale del script: + if len(sys.argv) == 2 and sys.argv[1] == "help": + show_help() + sys.exit(0) + # Si se ejecuta el script con más o menos de 2 parámetros, se muestra un error y la ayuda, y se sale del script: + elif len(sys.argv) != 3: + print(f"{script_name} Error: Formato incorrecto: Se debe especificar 2 parámetros") + show_help() + sys.exit(1) + + + +def rename_image(image_original_name, image_new_name): + """ Asigna el nuevo nombre especificado a la imagen y a todos los archivos asociados. + Al archivo ".info.checked" también le cambia la extensión a ".info", + para poder actualizar la información del repositorio posteriormente. + """ + try: + journal.send("renameImage.py: Renaming image files...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + + # Creamos una lista con las extensiones de los archivos de la imagen (menos "info.checked"): + extensions = ['.img', '.img.full.sum', '.img.size', '.img.sum', '.img.torrent'] + + # Renombramos cada uno de los archivos (menos "info.checked"): + for ext in extensions: + os.rename(f"{repo_path}{image_original_name}{ext}", f"{repo_path}{image_new_name}{ext}") + + # Renombramos separadamente el archivo "info.checked" (porque también le cambiamos la extensión a "info"): + os.rename(f"{repo_path}{image_original_name}.img.info.checked", f"{repo_path}{image_new_name}.img.info") + + except Exception as error: + journal.send(f"renameImage.py: Rename Image exception: {error}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + sys.exit(2) + + + +def update_repo_info(): + """ Actualiza la información del repositorio, ejecutando el script "updateRepoInfo.py". + """ + try: + journal.send("renameImage.py: Running script 'updateRepoInfo.py'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + result = subprocess.run(['python3', update_repo_script], check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + except subprocess.CalledProcessError as error: + journal.send(f"renameImage.py: 'updateRepoInfo.py' error: {error}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + sys.exit(3) + except Exception as error: + journal.send(f"renameImage.py: 'updateRepoInfo.py' exception: {error}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + sys.exit(4) + + + +# -------------------------------------------------------------------------------------------- +# MAIN +# -------------------------------------------------------------------------------------------- + + +def main(): + """ + """ + # Evaluamos si se ha enviado la cantidad correcta de parámetros, y en el formato correcto: + check_params() + + # Almacenamos el nombre original de la imagen, y el nuevo nombre a asignar (desde los parámetros): + image_original_name = sys.argv[1] + image_new_name = sys.argv[2] + + # Renombramos la imagen y sus archivos asociados: + rename_image(image_original_name, image_new_name) + + # Actualizamos la información del repositorio, ejecutando el script "updateRepoInfo.py": + update_repo_info() + + + +# -------------------------------------------------------------------------------------------- + +if __name__ == "__main__": + main() + +# --------------------------------------------------------------------------------------------