diff --git a/README.md b/README.md index b8d802c..71116bb 100644 --- a/README.md +++ b/README.md @@ -30,11 +30,8 @@ El presente documento detalla los endpoints de la API, con sus respectivos pará 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/send-p2p` 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) - +12. [Ver Estado de Transmisiones Multicast-P2P](#ver-estado-de-transmisiones-multicast-p2p) - +13. [Cancelar Transmisión Multicast-P2P](#cancelar-transmisión-multicast-p2p) - --- ### Obtener Información de todas las Imágenes @@ -202,21 +199,21 @@ curl -X POST -H "Authorization: $API_KEY" -H "Content-Type: application/json" -d ### Importar una Imagen Se importará una imagen de un repositorio remoto al repositorio local. -Se puede intentar hacer con el script "**importimage**", que actualmente no se utiliza, pero seguramente habrá que modificarlo. +Se puede hacer con el script "**importImage.py**", que hemos programado recientemente. **URL:** `/ogrepository/v1/images/import-image` **Método HTTP:** POST **Cuerpo de la Solicitud (JSON):** -- **user**: Usuario con el que acceder al repositorio remoto (por defecto, usuario local). -- **repo**: IP o hostname del repositorio remoto. - **image**: Nombre de la imagen (con extensión). -- **ou_subdir**: Subdirectorio correspondiente a la OU (o "none" si no es el caso). - +- **ou_subdir**: Subdirectorio correspondiente a la OU (o "none" si no es el caso). +- **repo**: IP o hostname del repositorio remoto. +- **user**: Usuario con el que acceder al repositorio remoto. + **Ejemplo de Solicitud:** ```bash -curl -X POST -H "Authorization: $API_KEY" -H "Content-Type: application/json" -d '{"user":"user_name", "repo":"192.168.56.100", "image":"Windows10.img", "ou_subdir":"none"}' http://example.com/ogrepository/v1/images/import-image +curl -X POST -H "Authorization: $API_KEY" -H "Content-Type: application/json" -d '{"image":"Windows10.img", "ou_subdir":"none", "repo":"192.168.56.100", "user":"user_name"}' http://example.com/ogrepository/v1/images/import-image ``` **Respuestas:** - **Código 500 Internal Server Error:** Ocurrió un error al importar la imagen. @@ -350,82 +347,6 @@ curl -X POST -H "Authorization: $API_KEY" -H "Content-Type: application/json" -d - **Código 200 OK:** La imagen se ha chequeado exitosamente. - **Código 200 KO:** La imagen se ha chequeado correctamente, pero no ha pasado el test. ---- -### Exportar una Imagen - -Se exportará una imagen del repositorio local a un repositorio remoto. -Se debe crear un script que realice dicha tarea (o se puede intentar utilizar el script "**importimage**", que realiza la acción contraria, pero que actualmente no se utiliza). -**NOTA**: Aunque no se indica en el pliego, entendemos que también será necesario especificar credenciales de acceso al repositorio remoto como parámetros de entrada. - -**URL:** `/ogrepository/v1/images/export-image` -**Método HTTP:** POST - -**Cuerpo de la Solicitud (JSON):** -- **repo**: IP o hostname del repositorio remoto. -- **ou**: Unidad Organizativa del repositorio remoto. -- **image**: Nombre de la imagen (con extensión). -- **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 '{"repo":"192.168.56.200", "ou":"OU_Ejemplo", "image":"Windows10.img", "ou_subdir":"none"}' http://example.com/ogrepository/v1/images/export-image -``` -**Respuestas:** -- **Código 500 Internal Server Error:** Ocurrió un error al exportar la imagen. -- **Código 400 Bad Request:** No se ha encontrado la imagen y/o el equipo remoto especificados. -- **Código 200 OK:** La imagen se ha exportado exitosamente. - ---- -### Definir Imagen Global - -Se marcará como "global" la imagen especificada como parámetro. -En principio, esto requerirá agregar una nueva clave al archivo "**/opt/opengnsys/etc/repoinfo.json**" (por ejemplo, "scope"), modificando el script "**updateRepoInfo.py**", para que realice dicha modificación. -También debe crearse un script que realice la definición de la imagen especificada (modificando el valor de la nueva clave del archivo "**/opt/opengnsys/etc/repoinfo.json**", de "local" a "global", por ejemplo). -Además, deberá llamarse a un script que exporte dicha imagen a los demás repositorios gestionados por el servidor de administración (que aun no está creado). - -**URL:** `/ogrepository/v1/images/set-global` -**Método HTTP:** PUT - -**Cuerpo de la Solicitud (JSON):** -- **image**: Nombre de la imagen (con extensión). -- **ou_subdir**: Subdirectorio correspondiente a la OU (o "none" si no es el caso). - -**Ejemplo de Solicitud:** - -```bash -curl -X PUT -H "Authorization: $API_KEY" -H "Content-Type: application/json" -d '{"image":"Windows10.img", "ou_subdir":"none"}' http://example.com/ogrepository/v1/images/set-global -``` -**Respuestas:** -- **Código 500 Internal Server Error:** Ocurrió un error al definir la imagen. -- **Código 400 Bad Request:** No se ha encontrado la imagen especificada. -- **Código 200 OK:** La definición se realizó exitosamente. - ---- -### Definir Imagen Local - -Se marcará como "local" la imagen especificada como parámetro, que previamente habría sido marcada como "global" (ya que entiendo que de forma predeterminada, todas las imágenes estarán marcadas como "local"). -Como comentábamos en el endpoint precedentte, esto requerirá agregar una nueva clave al archivo "**/opt/opengnsys/etc/repoinfo.json**" (por ejemplo, "scope"), modificando el script "**updateRepoInfo.py**", para que realice dicha modificación. -También debe crearse un script que realice la definición de la imagen especificada (modificando el valor de la nueva clave del archivo "**/opt/opengnsys/etc/repoinfo.json**", de "global" a "local", por ejemplo). -Este endpoint deberá ser llamado en todos los repositorios gestionados por el mismo servidor de administración (para que todos hagan la modificación). - -**URL:** `/ogrepository/v1/images/set-local` -**Método HTTP:** PUT - -**Cuerpo de la Solicitud (JSON):** -- **image**: Nombre de la imagen (con extensión). -- **ou_subdir**: Subdirectorio correspondiente a la OU (o "none" si no es el caso). - -**Ejemplo de Solicitud:** - -```bash -curl -X PUT -H "Authorization: $API_KEY" -H "Content-Type: application/json" -d '{"image":"Windows10.img", "ou_subdir":"none"}' http://example.com/ogrepository/v1/images/set-local -``` -**Respuestas:** -- **Código 500 Internal Server Error:** Ocurrió un error al definir la imagen. -- **Código 400 Bad Request:** No se ha encontrado la imagen especificada. -- **Código 200 OK:** La definición se realizó exitosamente. - --- ### Ver Estado de Transmisiones Multicast-P2P diff --git a/py_scripts/importImage.py b/py_scripts/importImage.py new file mode 100644 index 0000000..934b308 --- /dev/null +++ b/py_scripts/importImage.py @@ -0,0 +1,223 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +""" +Este script importa la imagen especificada como primer parámetro (y sus archivos asociados), desde el repositorio remoto especificado como segundo parámetro, + con las credenciales del usuario especificado como tercer parámetro (en principio, mediante claves). +Es muy similar al script bash original (cuyo nombre es "importimage", a secas), pero con ciertas diferencias. +Al acabar, llama al script "updateRepoInfo.py", para actualizar la información del repositorio. + +Librerías Python requeridas: "paramiko" (se puede instalar con "sudo apt install python3-paramiko") + + Parámetros +------------ +sys.argv[1] - Nombre completo de la imagen a importar (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] - IP o hostname del repositorio remoto. + - Ejemplo1: 192.168.56.100 + - Ejemplo2: remote_repo + +sys.argv[3] - Usuario con el que conectar al repositorio remoto. + - Ejemplo1: remote_user + - Ejemplo2: root + + Sintaxis +---------- +./importImage.py [ou_subdir/]image_name|/image_path/image_name remote_host remote_user + + Ejemplos + --------- +./importImage.py image1.img 192.168.56.100 user +./importImage.py /opt/opengnsys/images/image1.img 192.168.56.100 user +./importImage.py ou_subdir/image1.img remote_hostname user +./importImage.py /ou_subdir/image1.img remote_hostname root +./importImage.py /opt/opengnsys/images/ou_subdir/image1.img remote_hostname root +""" + +# -------------------------------------------------------------------------------------------- +# IMPORTS +# -------------------------------------------------------------------------------------------- + +import warnings +warnings.filterwarnings("ignore") +import os +import sys +import subprocess +import paramiko +import warnings + + +# -------------------------------------------------------------------------------------------- +# VARIABLES +# -------------------------------------------------------------------------------------------- + +script_name = os.path.basename(__file__) +repo_path = '/opt/opengnsys/images/' +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 remote_host remote_user + Ejemplo1: {script_name} image1.img 192.168.56.100 user + Ejemplo2: {script_name} /opt/opengnsys/images/image1.img 192.168.56.100 user + Ejemplo3: {script_name} ou_subdir/image1.img remote_hostname user + Ejemplo4: {script_name} /ou_subdir/image1.img remote_hostname root + Ejemplo5: {script_name} /opt/opengnsys/images/ou_subdir/image1.img remote_hostname root + """ + 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 3 parámetros, se muestra un error y la ayuda, y se sale del script: + elif len(sys.argv) != 4: + print(f"{script_name} Error: Formato incorrecto: Se debe especificar 3 parámetros") + show_help() + sys.exit(1) + + + +def build_file_path(): + """ Construye la ruta completa al archivo a importar + (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 import_image(file_path, remote_host, remote_user): + """ Conecta al repositorio remoto por SSH e inicia un cliente SFTP. + Comprueba que la imagen no esté bloqueada, en cuyo caso la descarga (junto con sus archivos asociados). + """ + # 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.checked'] + + # Iniciamos un cliente SSH: + ssh_client = paramiko.SSHClient() + # Establecemos la política por defecto para localizar la llave del host localmente: + ssh_client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) + + # Intentamos conectar con el equipo remoto por SSH, e iniciar un cliente SFTP, + try: # y en caso de fallar devolvemos un error y salimos del script: + ssh_client.connect(remote_host, 22, remote_user) # Así se hace con claves + #ssh_client.connect(remote_host, 22, remote_user, 'opengnsys') # Así se haría con password + sftp_client = ssh_client.open_sftp() + except Exception as error_description: + print(f"Connection has returned an exception: {error_description}") + sys.exit(2) + + # Comprobamos si la imagen existe en el equipo remoto, y en caso contrario devolvemos un error y salimos del script: + try: + sftp_client.stat(file_path) + except IOError: + print("Remote image doesn't exist") + sys.exit(3) + + # Comprobamos si la imagen remota está bloqueada, en cuyo caso devolvemos un error y salimos del script, + try: # y en caso contrario la importamos (junto con todos sus archivos asociados): + sftp_client.stat(f"{file_path}.lock") + print("Remote image is locked.") + sys.exit(4) + except IOError: + print("Importing remote image...") + open(f"{file_path}.lock", "w").close() # Creamos un archivo de bloqueo + for ext in extensions: + sftp_client.get(f"{file_path}{ext}", f"{file_path}{ext}") + + # Cerramos el cliente SSH y el cliente SFTP: + ssh_client.close() + sftp_client.close() + + + +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(2) + except Exception as error: + print(f"Se ha producido un error inesperado: {error}") + sys.exit(3) + + + +# -------------------------------------------------------------------------------------------- +# 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 importar: + file_path = build_file_path() + + # Almacenamos la IP/hostname del repositorio remoto, y el usuario remoto (desde los parámetros): + remote_host = sys.argv[2] + remote_user = sys.argv[3] + + # Evaluamos si la ruta de la imagen tiene 5 barras, en cuyo caso corresponderá a una imagen basada en OU, + # y almacenamos el nombre del directorio correspondiente a la OU: + if file_path.count('/') == 5: + ou_subdir = file_path.split('/')[4] + # Si no existe un directorio correspondiente a la OU en el repo local, lo creamos: + if not os.path.exists(f"{repo_path}{ou_subdir}"): + os.mkdir(f"{repo_path}{ou_subdir}", 0o755) + + # Importamos la imagen del repositorio remoto: + import_image(file_path, remote_host, remote_user) + + # Eliminamos el archivo de bloqueo: + os.remove(f"{file_path}.lock") + + # Actualizamos la información del repositorio, ejecutando el script "updateRepoInfo.py": + print("Updating Repository Info...") + update_repo_info() + + + +# -------------------------------------------------------------------------------------------- + +if __name__ == "__main__": + main() + +# --------------------------------------------------------------------------------------------