diff --git a/README.md b/README.md index fa164fe..d91579e 100644 --- a/README.md +++ b/README.md @@ -23,17 +23,18 @@ El presente documento detalla los endpoints de la API, con sus respectivos pará 2. [Obtener Información de una Imagen concreta](#obtener-información-de-una-imagen-concreta) - `GET /ogrepository/v1/images/{name}` 3. [Actualizar Información del Repositorio](#actualizar-información-del-repositorio) - `PUT /ogrepository/v1/images` 4. [Eliminar una Imagen](#eliminar-una-imagen) - `DELETE /ogrepository/v1/images/{name}` -5. [Importar una Imagen](#importar-una-imagen) - `POST /ogrepository/v1/images/import-image` -6. [Enviar una Imagen mediante UDPcast](#enviar-una-imagen-mediante-udpcast) - `POST /ogrepository/v1/images/{protocol}` -7. [Enviar una Imagen mediante UFTP](#enviar-una-imagen-mediante-uftp) - `POST /ogrepository/v1/images/{protocol}` -8. [Crear archivo ".torrent"](#crear-archivo-torrent) - `POST /ogrepository/v1/images/create-torrent` -9. [Enviar una Imagen mediante P2P](#enviar-una-imagen-mediante-p2p) - `POST /ogrepository/v1/images/{protocol}` -10. [Chequear integridad de Imagen](#chequear-integridad-de-imagen) - `GET /ogrepository/v1/images/check-image` -11. [Exportar una Imagen](#exportar-una-imagen) - `POST /ogrepository/v1/images/export-image` -12. [Definir Imagen Global](#definir-imagen-global) - `PUT /ogrepository/v1/images/set-global` -13. [Definir Imagen Local](#definir-imagen-local) - `PUT /ogrepository/v1/images/set-local` -14. [Ver Estado de Transmisiones Multicast-P2P](#ver-estado-de-transmisiones-multicast-p2p) - -15. [Cancelar Transmisión Multicast-P2P](#cancelar-transmisión-multicast-p2p) - +5. [Recuperar una Imagen](#recuperar-una-imagen) - `POST /ogrepository/v1/images/{name}` +6. [Importar una Imagen](#importar-una-imagen) - `POST /ogrepository/v1/images/import-image` +7. [Enviar una Imagen mediante UDPcast](#enviar-una-imagen-mediante-udpcast) - `POST /ogrepository/v1/images/{protocol}` +8. [Enviar una Imagen mediante UFTP](#enviar-una-imagen-mediante-uftp) - `POST /ogrepository/v1/images/{protocol}` +9. [Crear archivo ".torrent"](#crear-archivo-torrent) - `POST /ogrepository/v1/images/create-torrent` +10. [Enviar una Imagen mediante P2P](#enviar-una-imagen-mediante-p2p) - `POST /ogrepository/v1/images/{protocol}` +11. [Chequear integridad de Imagen](#chequear-integridad-de-imagen) - `GET /ogrepository/v1/images/check-image` +12. [Exportar una Imagen](#exportar-una-imagen) - `POST /ogrepository/v1/images/export-image` +13. [Definir Imagen Global](#definir-imagen-global) - `PUT /ogrepository/v1/images/set-global` +14. [Definir Imagen Local](#definir-imagen-local) - `PUT /ogrepository/v1/images/set-local` +15. [Ver Estado de Transmisiones Multicast-P2P](#ver-estado-de-transmisiones-multicast-p2p) - +16. [Cancelar Transmisión Multicast-P2P](#cancelar-transmisión-multicast-p2p) - --- ### Obtener Información de todas las Imágenes @@ -134,8 +135,8 @@ curl -X GET -H "Authorization: $API_KEY" http://example.com/ogrepository/v1/imag ### Actualizar Información del Repositorio Se actualizará la información de las imágenes almacenadas en el repositorio, reflejándola en el archivo "**/opt/opengnsys/etc/repoinfo.json**". -Se puede hacer con el script "**checkrepo**", que actualmente se ejecuta a cada minuto por crontab (indirectamente, porque es llamado por el script "**deletepreimage**", que es el que realmente se ejecuta por crontab). -Creemos que este endpoint debe ser llamado por ogCore u ogLive cada vez que se haya creado una imagen, y ejecutado desde el propio ogRepository cada vez que se elimine una imagen. +Se puede hacer con el script "**updateRepoInfo.py**", que hemos programado recientemente (y que es similar al script bash original "**checkrepo**"). +Este endpoint es llamado por el script "**deleteImage.py**" (para actualizar la información cada vez que se elimine una imagen), y creemos que también debe ser llamado por ogCore u ogLive cada vez que se haya creado una imagen. **URL:** `/ogrepository/v1/images` **Método HTTP:** PUT @@ -152,25 +153,48 @@ curl -X PUT -H "Authorization: $API_KEY" http://example.com/ogrepository/v1/imag --- ### Eliminar una Imagen -Se eliminará la imagen especificada como parámetro. -Se puede hacer con el script "**deleteimage**", que actualmente no se utiliza (lo que se hace ahora es eliminar todas las imágenes marcadas con ".delete", mediante el script "deletepreimage", que se ejecuta por crontab a cada minuto). -Además, el script "deleteimage" debería llamar al script "**checkrepo**", para actualizar la información del repositorio una vez eliminada la imagen. - -**NOTA**: En el pliego se solicita una función "papelera", para lo que habría que modificar los scripts existentes (y posiblemente crear otros endpoints, como "recuperar imagen de la papelera", por ejemplo). +Se eliminará la imagen especificada como parámetro, pudiendo eliminarla permanentemente o enviarla a la papelera. +Se puede hacer con el script "**deleteimage.py**", que hemos programado recientemente (y que incluye la funcionalidad "papelera"), y que a su vez llama al script "**updateRepoInfo.py**", para actualizar la información del repositorio. **URL:** `/ogrepository/v1/images/{name}` **Método HTTP:** DELETE +**Cuerpo de la Solicitud (JSON):** +- **ou_subdir**: Subdirectorio correspondiente a la OU (o "none" si no es el caso). +- **method**: Método de eliminación (puede ser "trash" o "permanent"). + **Ejemplo de Solicitud:** ```bash -curl -X DELETE -H "Authorization: $API_KEY" http://example.com/ogrepository/v1/images/Windows10 +curl -X DELETE -H "Authorization: $API_KEY" -H "Content-Type: application/json" -d '{"ou_subdir":"none", "method":"trash"}' http://example.com/ogrepository/v1/images/Windows10.img ``` **Respuestas:** - **Código 500 Internal Server Error:** Ocurrió un error al eliminar la imagen. - **Código 400 Bad Request:** No se ha encontrado la imagen especificada. - **Código 200 OK:** La imagen se eliminó exitosamente. +--- +### Recuperar una Imagen + +Se recuperará la imagen especificada como parámetro, desde la papelera. +Se puede hacer con el script "**recoverImage.py**", que hemos programado recientemente, y que a su vez llama al script "**updateRepoInfo.py**", para actualizar la información del repositorio. + +**URL:** `/ogrepository/v1/images/{name}` +**Método HTTP:** POST + +**Cuerpo de la Solicitud (JSON):** +- **ou_subdir**: Subdirectorio correspondiente a la OU (o "none" si no es el caso). + +**Ejemplo de Solicitud:** + +```bash +curl -X POST -H "Authorization: $API_KEY" -H "Content-Type: application/json" -d '{"ou_subdir":"none"}' http://example.com/ogrepository/v1/images/Windows10.img +``` +**Respuestas:** +- **Código 500 Internal Server Error:** Ocurrió un error al recuperar la imagen. +- **Código 400 Bad Request:** No se ha encontrado la imagen especificada. +- **Código 200 OK:** La imagen se recuperó exitosamente. + --- ### Importar una Imagen diff --git a/py_scripts/deleteImage.py b/py_scripts/deleteImage.py new file mode 100644 index 0000000..a17f71f --- /dev/null +++ b/py_scripts/deleteImage.py @@ -0,0 +1,240 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +""" +Este script elimina la imagen que recibe como parámetro (y todos sus archivos asociados), moviendo los archivos a la papelera + (respetando el subdirectorio correspondiente a la OU, si fuera el caso), o eliminándolos permanentemente si se especifica el parámetro "-p". +Es similar al script bash original (cuyo nombre es "deleteimage", a secas), pero este no incluía la funcionalidad papelera. +Llama al script "updateRepoInfo.py", para actualizar la información del repositorio. + + Parámetros +------------ +sys.argv[1] - Nombre completo de la imagen a eliminar (con o sin ruta), pero incluyendo el subdirectorio correspondiente a la OU, si es el caso. + - Ejemplo1: image1.img + - Ejemplo2: /opt/opengnsys/images/image1.img + - Ejemplo3: ou_subdir/image1.img + - Ejemplo4: /ou_subdir/image1.img + - Ejemplo5: /opt/opengnsys/images/ou_subdir/image1.img + +sys.argv[2] - Parámetro opcional para especificar que la eliminación sea permanente (sin papelera). + - Ejemplo: -p + + Sintaxis +---------- +./deleteImage.py [ou_subdir/]image_name|/image_path/image_name [-p] + + Ejemplos + --------- +./deleteImage.py image1.img -p +./deleteImage.py /opt/opengnsys/images/image1.img -p +./deleteImage.py ou_subdir/image1.img -p +./deleteImage.py /ou_subdir/image1.img +./deleteImage.py /opt/opengnsys/images/ou_subdir/image1.img +""" + +# -------------------------------------------------------------------------------------------- +# IMPORTS +# -------------------------------------------------------------------------------------------- + +import os +import sys +import shutil +import pwd +import grp +import subprocess + + +# -------------------------------------------------------------------------------------------- +# VARIABLES +# -------------------------------------------------------------------------------------------- + +script_name = os.path.basename(__file__) +repo_path = '/opt/opengnsys/images/' +trash_path = '/opt/opengnsys/images_trash/' +update_repo_script = '/opt/opengnsys/py_scripts/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} [ou_subdir/]image_name|/image_path/image_name [-p] + Ejemplo1: {script_name} image1.img -p + Ejemplo2: {script_name} /opt/opengnsys/images/image1.img -p + Ejemplo3: {script_name} ou_subdir/image1.img -p + Ejemplo4: {script_name} /ou_subdir/image1.img + Ejemplo5: {script_name} /opt/opengnsys/images/ou_subdir/image1.img + """ + 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 sin parámetros, se muestra un error y la ayuda, y se sale del script: + if len(sys.argv) == 1: + print(f"{script_name} Error: Formato incorrecto: Se debe especificar al menos 1 parámetro") + show_help() + sys.exit(0) + # Si se ejecuta el script con el parámetro "help", se muestra la ayuda, y se sale del script: + elif len(sys.argv) == 2 and sys.argv[1] == "help": + show_help() + sys.exit(1) + # Si se ejecuta el script con el parámetro "help" y más parámetros, se muestra un error y la ayuda, y se sale del script: + elif len(sys.argv) > 2 and sys.argv[1] == "help": + print(f"{script_name} Error: Formato incorrecto: Para invocar la ayuda, se debe especificar 'help' como único parámetro") + show_help() + sys.exit(2) + # Si se ejecuta el script con 2 parámetros y el segundo es diferente de "-p", se muestra un error y la ayuda, y se sale del script: + elif len(sys.argv) == 3 and sys.argv[2] != "-p": + print(f"{script_name} Error: Formato incorrecto: El segundo parámetro solo puede ser '-p'") + show_help() + sys.exit(3) + # Si se ejecuta el script con más 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 1 o 2 parámetros") + show_help() + sys.exit(4) + + +def build_file_path(): + """ Construye la ruta completa al archivo a eliminar + (agregando "/opt/opengnsys/images/" si no se ha especificado en el parámetro). + """ + param_path = sys.argv[1] + # Si la ruta comienza con una barra, pero que no corresponde a "repo_path" + # (porque corresponderá al subdirectorio de una OU), eliminamos la barra: + if param_path.startswith('/') and not param_path.startswith(repo_path): + param_path = param_path.lstrip('/') + # Construimos la ruta completa: + if not param_path.startswith(repo_path): + file_path = os.path.join(repo_path, param_path) + else: + file_path = param_path + return file_path + + +def create_trash_folder(): + """ Crea el directorio correspondiente a la papelera, y le asigna propietarios y permisos. + Evidentemente, esta función solo es llamada cuando no existe el directorio. + """ + # Obtenemos el UID del usuario "root" y el GID del grupo "opengnsys": + uid = pwd.getpwnam('root').pw_uid + gid = grp.getgrnam('opengnsys').gr_gid + # Creamos el directorio correspondiente a la papelera: + os.mkdir(trash_path) + # Asignamos el usuario y el grupo propietarios del directorio: + os.chown(trash_path, uid, gid) + # Asignamos permisos "775" al directorio : + os.chmod(trash_path, 0o775) + + +def delete_normal_image(file_path, method, extensions): + """ Elimina la imagen "normal" que recibe en el parámetro "file_path", y todos sus archivos asociados, + moviéndolos a la papelera o eliminándolos permanentemente (en función del parámetro "method"). + """ + # Iteramos las extensiones de los archivos, y construimos la ruta completa de cada uno de ellos: + for ext in extensions: + file_to_remove = f"{file_path}{ext}" + # Si el archivo actual existe, lo eliminamos o lo movemos a la papelera + # (dependiendo del valor del parámetro "method"): + if os.path.exists(file_to_remove): + if method == 'trash': + shutil.move(file_to_remove, trash_path) + elif method == 'permanent': + os.remove(file_to_remove) + + +def delete_ou_image(file_path, method, extensions): + """ Elimina la imagen basada en OU que recibe en el parámetro "file_path", y todos sus archivos asociados, + moviéndolos a la papelera o eliminándolos permanentemente (en función del parámetro "method"). + """ + # Iteramos las extensiones de los archivos, y construimos la ruta completa de cada uno de ellos: + for ext in extensions: + file_to_remove = f"{file_path}{ext}" + # Si el archivo actual existe, lo eliminamos o lo movemos a la papelera (dependiendo del valor del parámetro "method"), + # y en el último caso lo situamos en un subdirectorio correspondiente a la OU: + if os.path.exists(file_to_remove): + if method == 'trash': + ou_subdir = file_to_remove.split('/')[4] + # Comprobamos si en la papelera existe un subdirectorio correspondiente a la OU (y si no existe lo creamos): + if not os.path.exists(f"{trash_path}{ou_subdir}"): + os.mkdir(f"{trash_path}{ou_subdir}") + shutil.move(file_to_remove, f"{trash_path}{ou_subdir}") + elif method == 'permanent': + os.remove(file_to_remove) + + +def update_repo_info(): + """ Actualiza la información del repositorio, ejecutando el script "updateRepoInfo.py". + Como se ve, es necesario que el script se ejecute como sudo, o dará error. + """ + try: + result = subprocess.run(['sudo', 'python3', update_repo_script], check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + except subprocess.CalledProcessError as error: + print(f"Error Output: {error.stderr.decode()}") + sys.exit(3) + except Exception as error: + print(f"Se ha producido un error inesperado: {error}") + sys.exit(4) + + + +# -------------------------------------------------------------------------------------------- +# MAIN +# -------------------------------------------------------------------------------------------- + + +def main(): + """ + """ + # Evaluamos si se ha enviado la cantidad correcta de parámetros, y en el formato correcto: + check_params() + + # Obtenemos la ruta completa al archivo a eliminar: + file_path = build_file_path() + + # Comprobamos si existe el directorio correspondiente a la papelera, y en caso contrario lo creamos: + if not os.path.exists(trash_path): + print("Creating trash folder...") + create_trash_folder() + + # Especificamos el método de eliminación (permanente o utilizando la papelera): + if len(sys.argv) == 3 and sys.argv[2] == "-p": + method = 'permanent' + else: + method = 'trash' + + # Creamos una lista con las extensiones de los archivos asociados a la imagen + # (incluyendo ninguna extensión, que corresponde a la propia imagen): + extensions = ['', '.sum', '.full.sum', '.torrent', '.info', '.info.checked'] + + # Evaluamos la cantidad de barras que hay en la ruta de la imagen, para diferenciar entre imágenes "normales" y basadas en OU + # (y llamamos a la función correspondiente para eliminarla): + if file_path.count('/') == 4: + print("Deleting normal image...") + delete_normal_image(file_path, method, extensions) + elif file_path.count('/') == 5: + print("Deleting OU based image...") + delete_ou_image(file_path, method, extensions) + + # Actualizamos la información del repositorio, ejecutando el script "updateRepoInfo.py": + print("Updating Repository Info...") + update_repo_info() + + + +# -------------------------------------------------------------------------------------------- + +if __name__ == "__main__": + main() + +# --------------------------------------------------------------------------------------------