From f055aaf69dc9d12ade2db3675769d41ee60fffcf Mon Sep 17 00:00:00 2001 From: Vadim Troshchinskiy Date: Mon, 28 Apr 2025 11:39:19 +0200 Subject: [PATCH] Git implementation and Swagger --- api/repo_api.py | 378 ++++++++++++++++++++++++++++++++++++++++++++--- api/swagger.yaml | 344 +++++++++++++++++++++++++++++++++++++++--- 2 files changed, 677 insertions(+), 45 deletions(-) diff --git a/api/repo_api.py b/api/repo_api.py index 0bc2c5f..64fa5fc 100644 --- a/api/repo_api.py +++ b/api/repo_api.py @@ -2,16 +2,16 @@ # -*- coding: utf-8 -*- """ - API de ogRepository, programada en Flask. + API de ogRepository, programada en Flask. Responde a peticiones HTTP (en principio, enviadas desde ogCore) mediante endpoints, que a su vez ejecutan los scripts Python almacenados en ogRepository. En ciertos casos, transforma los parámetros recibidos desde el portal, para adaptarlos a los que es necesario enviar a los scripts - (por ejemplo, a partir del ID de una imagen obtiene su nombre y su extensión). + (por ejemplo, a partir del ID de una imagen obtiene su nombre y su extensión). -Librerías Python requeridas: - flask (se puede instalar con "sudo apt install python3-flask") - - paramiko (se puede instalar con "sudo apt install python3-paramiko") +Librerías Python requeridas: - flask (se puede instalar con "sudo apt install python3-flask") + - paramiko (se puede instalar con "sudo apt install python3-paramiko") - requests (se puede instalar con "sudo apt install python3-requests") - No es necesario instalarlo en Ubuntu 24 - - flasgger (se puede instalar con "sudo apt install python3-flasgger") + - flasgger (se puede instalar con "sudo apt install python3-flasgger") """ # -------------------------------------------------------------------------------------------- @@ -48,6 +48,19 @@ trash_file = '/opt/opengnsys/ogrepository/etc/trashinfo.json' config_file = '/opt/opengnsys/ogrepository/etc/ogAdmRepo.cfg' + + +# -------------------------------------------------------------------------------------------- +# GIT +# -------------------------------------------------------------------------------------------- + +REPOSITORIES_BASE_PATH = "/opt/opengnsys/ogrepository/oggit/git/oggit/" + +import git +import pkgutil +import importlib + + # -------------------------------------------------------------------------------------------- # FUNCTIONS # -------------------------------------------------------------------------------------------- @@ -73,9 +86,9 @@ swagger = Swagger(app, template=swagger_template) def get_IPcore(): """ Obtiene el valor asociado a la variable "IPcore", desde el archivo '/opt/opengnsys/ogrepository/etc/ogAdmRepo.cfg'. Retorna la IP encontrada (que corresponde a la IP de ogCore), o un error (si no la encuentra). - """ + """ journal.send("Running function 'get_IPcore'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - + IPcore = None with open(config_file, 'r') as file: for line in file: @@ -327,7 +340,7 @@ def check_remote_backup(image_name, remote_ip, remote_user, remote_path, job_id) # Creamos un bucle infinito: while True: # Comprobamos si ya se ha copiado la imagen y cada uno de sus archivos asociados (y almacenamos el resultado): - try: + try: for ext in extensions: sftp_client.stat(f"{remote_path}{image_name}{ext}") all_files_copied = True @@ -601,7 +614,7 @@ def recall_ogcore(data): def check_file_exists(file_path): """ Comprueba la existencia del archivo cuya ruta recibe como parámetro. - Si el archivo existe devuelve "True", y si no devuelve "False". + Si el archivo existe devuelve "True", y si no devuelve "False". """ journal.send("Running function 'check_file_exists'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") @@ -671,7 +684,7 @@ def get_repo_status(): journal.send("Running script 'getRepoStatus.py'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") # Ejecutamos el script "getRepoStatus.py", y almacenamos el resultado: result = subprocess.run(['python3', f"{script_path}/getRepoStatus.py"], 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 'getRepoStatus.py' result OK (ReturnCode: 0)", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") @@ -973,7 +986,7 @@ def recover_image(): # Almacenamos el parámetro "ID_img" (enviado por JSON): json_data = json.loads(request.data) - image_id = json_data.get("ID_img") + image_id = json_data.get("ID_img") # Obtenemos el nombre y la extensión de la imagen: param_dict = get_image_params(image_id, "trash") @@ -1136,7 +1149,7 @@ def import_image(): if result.returncode is None: journal.send("Script 'importImage.py' result OK (ReturnCode: None)", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") journal.send("{'component':'ogRepo', 'severity':'INFO', 'http_code':'200', 'operation':'Run script importImage.py', 'desc':'Result OK (ReturnCode: None)'}", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api") - + # Si el resultado es correcto, llamamos a la función "check_lock_local" en un hilo paralelo # (para que compruebe si la imagen se ha acabado de importar exitosamente): journal.send("Calling function 'check_lock_local'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") @@ -1179,7 +1192,7 @@ def import_image(): def backup_image(): """ Este endpoint exporta la imagen especificada como primer parámetro (y todos sus archivos asociados), desde el servidor local a un equipo remoto (que no tiene por qué ser un repositorio). Para ello, ejecuta el script "backupImage.py", con el nombre de la imagen como primer parámetro, la IP o hostname del equipo remoto como segundo parámetro, - el usuario con el que conectar al equipo remoto como tercer parámetro, y la ruta remota en la que copiar la imagen como cuarto parámetro. + el usuario con el que conectar al equipo remoto como tercer parámetro, y la ruta remota en la que copiar la imagen como cuarto parámetro. """ journal.send("Running endpoint 'Hacer backup de una Imagen'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") @@ -1260,7 +1273,7 @@ def backup_image(): }), 200 else: journal.send("Script 'backupImage.py' result KO (Backup image failed)", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - journal.send("{'component':'ogRepo', 'severity':'ERROR', 'http_code':'500', 'operation':'Run script backupImage.py', 'desc':'Result KO (Backup image failed)'}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api") + journal.send("{'component':'ogRepo', 'severity':'ERROR', 'http_code':'500', 'operation':'Run script backupImage.py', 'desc':'Result KO (Backup image failed)'}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api") return jsonify({ "success": False, "error": "Backup image failed" @@ -1608,7 +1621,7 @@ def send_p2p(): "success": False, "error": "Tracker or Seeder (or both) not running" }), 500 - + # --------------------------------------------------------- @@ -1875,7 +1888,7 @@ def stop_p2p(): journal.send("Running script 'stopP2P.py'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") # Ejecutamos el script "stopP2P.py", y almacenamos el resultado (este script si que requiere ser ejecutado con "sudo"): result = subprocess.run(['sudo', 'python3', f"{script_path}/stopP2P.py"], 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 'stopP2P.py' result OK (ReturnCode: 0)", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") @@ -1909,7 +1922,7 @@ def convert_virtual_image(): """ Este endpoint convierte la imagen virtual especificada como parámetro en una imagen "img" como las que se generan desde OpenGnsys (con "partclone" y "lzop"), por lo que luego puede ser restaurada como cualquier otra imagen del repositorio. Para ello, ejecuta el script "convertVMtoIMG.py", con el nombre de la imagen virtual como primer parámetro, - y el sistema de archivos de la partición a clonar (en formato "blkid") como segundo parámetro. + y el sistema de archivos de la partición a clonar (en formato "blkid") como segundo parámetro. """ journal.send("Running endpoint 'Convertir imagen virtual a imagen OpenGnsys'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") @@ -1972,7 +1985,7 @@ def convert_virtual_image(): if result.returncode is None: journal.send("Script 'convertVMtoIMG.py' result OK (ReturnCode: None)", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") journal.send("{'component':'ogRepo', 'severity':'INFO', 'http_code':'200', 'operation':'Run script convertVMtoIMG.py', 'desc':'Result OK (ReturnCode: None)'}", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api") - + # Si el resultado es correcto, llamamos a la función "check_virtual_image_conversion" en un hilo paralelo # (para que compruebe si la imagen se ha acabado de convertir exitosamente): journal.send("Calling function 'check_virtual_image_conversion'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") @@ -2015,7 +2028,7 @@ def convert_virtual_image(): def convert_image_to_virtual(): """ Este endpoint convierte la imagen de OpenGnsys especificada como parámetro en una imagen virtual con la extensión especificada (".vdi", ".vmdk", etc). Para ello, ejecuta el script "convertIMGtoVM.py", con el nombre de la imagen "img" como primer parámetro, - y la extensión del disco virtual destino (sin punto) como segundo parámetro. + y la extensión del disco virtual destino (sin punto) como segundo parámetro. """ journal.send("Running endpoint 'Convertir imagen OpenGnsys a imagen virtual'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") @@ -2077,7 +2090,7 @@ def convert_image_to_virtual(): if result.returncode is None: journal.send("Script 'convertIMGtoVM.py' result OK (ReturnCode: None)", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") journal.send("{'component':'ogRepo', 'severity':'INFO', 'http_code':'200', 'operation':'Run script convertIMGtoVM.py', 'desc':'Result OK (ReturnCode: None)'}", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api") - + # Si el resultado es correcto, llamamos a la función "check_virtual_image_reconversion" en un hilo paralelo # (para que compruebe si la imagen se ha acabado de convertir exitosamente): journal.send("Calling function 'check_virtual_image_reconversion'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") @@ -2119,7 +2132,7 @@ def convert_image_to_virtual(): @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. + 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") @@ -2185,6 +2198,329 @@ def rename_image(): +# ----------------------------------------------------------- +# ____ _ _ +# / ___(_) |_ +# | | _| | __| +# | |_| | | |_ +# \____|_|\__| +# +# ----------------------------------------------------------- + + +@app.route("/ogrepository/v1/git/repositories", methods=['GET']) +def git_list_repositories(): + """ + Retrieve a list of Git repositories. + + This endpoint scans the OpenGnsys image path for directories that + appear to be Git repositories (i.e., they contain a "HEAD" file). + It returns a JSON response containing the names of these repositories. + + Returns: + Response: A JSON response with a list of repository names or an + error message if the repository storage is not found. + - 200 OK: When the repositories are successfully retrieved. + - 500 Internal Server Error: When the repository storage is not found. + + Example JSON response: + { + "repositories": ["repo1", "repo2"] + } + """ + journal.send("Running endpoint 'Obtener Información de todos los repositorios Git'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + + if not os.path.isdir(REPOSITORIES_BASE_PATH): + journal.send(f"Can't list repositories. Repository storage at {REPOSITORIES_BASE_PATH} not found", PRIORITY=journal.LOG_ERROR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + return jsonify({"error": "Repository storage not found, git functionality may not be installed."}), 500 + + repos = [] + for entry in os.scandir(REPOSITORIES_BASE_PATH): + if entry.is_dir(follow_symlinks=False) and os.path.isfile(os.path.join(entry.path, "HEAD")): + name = entry.name + if name.endswith(".git"): + name = name[:-4] + + repos = repos + [name] + + + journal.send(f"Returning {len(repos)} repositories", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + + return jsonify({ + "repositories": repos + }), 200 + +def _load_module(module_name): + import importlib + return importlib.util.find_spec(module_name) is not None + return False + +def _load_installer(): + return _load_module("opengnsys-git-installer") + + +@app.route("/ogrepository/v1/git/repositories", methods=['POST']) +def git_create_repository(): + """ + Create a new Git repository. + + This endpoint creates a new Git repository with the specified name. + If the repository already exists, it returns a status message indicating so. + + Args: + repo (str): The name of the repository to be created. + + Returns: + Response: A JSON response with a status message and HTTP status code. + - 200: If the repository already exists. + - 201: If the repository is successfully created. + """ + data = request.json + + if data is None: + journal.send(f"Can't create repository, JSON post data missing", PRIORITY=journal.LOG_ERROR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + return jsonify({"error" : "Parameters missing"}), 400 + + repo = data["name"] + + repo_path = os.path.join(REPOSITORIES_BASE_PATH, repo + ".git") + if os.path.isdir(repo_path): + journal.send(f"Can't create repository {repo}, already exists at {repo_path}", PRIORITY=journal.LOG_ERROR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + return jsonify({"status": "Repository already exists"}), 200 + + _import_installer() + installer = OpengnsysGitInstaller() + installer.add_forgejo_repo(repo) + + journal.send(f"Repository {repo} created", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + + return jsonify({"status": "Repository created"}), 201 + +@app.route("/ogrepository/v1/git/repositories", methods=['DELETE']) +def git_delete_repository(): + return jsonify({"error" : "Not implemented"}), 500 + +@app.route("/ogrepository/v1/git/repositories//sync", methods=['POST']) +def git_sync_repository(repo): + return jsonify({"error" : "Not implemented"}), 500 + +@app.route("/ogrepository/v1/git/repositories//backup", methods=['POST']) +def git_backup_repository(repo): + return jsonify({"error" : "Not implemented"}), 500 + +@app.route("/ogrepository/v1/git/repositories//compact", methods=['POST']) +def git_compact_repository(repo): + return jsonify({"error" : "Not implemented"}), 500 + +@app.route("/ogrepository/v1/git/repositories//branches", methods=['GET']) +def git_get_branches(repo): + """ + Retrieve the list of branches for a given repository. + + Args: + repo (str): The name of the repository. + + Returns: + Response: A JSON response containing a list of branch names or an error message if the repository is not found. + - 200: A JSON object with a "branches" key containing a list of branch names. + - 404: A JSON object with an "error" key containing the message "Repository not found" if the repository does not exist. + """ + repo_path = os.path.join(REPOSITORIES_BASE_PATH, repo + ".git") + if not os.path.isdir(repo_path): + journal.send(f"Can't list repositories. Repository storage at {REPOSITORIES_BASE_PATH} not found", PRIORITY=journal.LOG_ERROR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + return jsonify({"error": "Repository not found"}), 404 + + git_repo = git.Repo(repo_path) + git_repo.git.config('--global', '--add', 'safe.directory', repo_path) + + + branches = [] + for branch in git_repo.branches: + branches = branches + [branch.name] + + journal.send(f"Returning {len(branches)} branches", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + return { + "branches": branches + } + +@app.route("/ogrepository/v1/git/repositories//branches/", methods=['POST']) +def git_create_branch(repo, branch): + """Create a given branch in a given repository + + Args: + repo (str): The name of the repository. + + Returns: + Response: A JSON response containing a creation status + - 201: A JSON object with a "status" key containing "created" + - 404: A JSON object with an "error" key containing the message "Repository not found" + - 409: A JSON object with an "error" key containing the message "Tag already exists" + """ + + repo_path = os.path.join(REPOSITORIES_BASE_PATH, repo + ".git") + if not os.path.isdir(repo_path): + journal.send(f"Can't create branch. Repository storage at {REPOSITORIES_BASE_PATH} not found", PRIORITY=journal.LOG_ERROR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + return jsonify({"error": "Repository not found"}), 404 + + git_repo = git.Repo(repo_path) + git_repo.git.config('--global', '--add', 'safe.directory', repo_path) + + data = request.json + if data is None: + journal.send(f"Can't create branch. JSON post data missing", PRIORITY=journal.LOG_ERROR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + return jsonify({"error" : "Parameters missing"}), 400 + + if not "commit" in data: + journal.send(f"Can't create branch. Commit parameter missing", PRIORITY=journal.LOG_ERROR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + return jsonify({"error" : "commit parameter missing"}), 400 + + if branch in git_repo.branches: + journal.send(f"Can't create branch. Already found in repository {repo}", PRIORITY=journal.LOG_ERROR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + return jsonify({"error": "Branch already exists"}), 409 + + git_repo.create_tag(branch, ref = data["commit"]) + + journal.send(f"Branch {branch} created in repo {repo}", PRIORITY=journal.LOG_ERROR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + return jsonify({"status": "created"}), 201 + +@app.route("/ogrepository/v1/git/repositories//branches/", methods=['DELETE']) +def git_delete_branch(repo, branch): + """Delete a given branch in a given repository + + Args: + repo (str): The name of the repository. + + Returns: + Response: A JSON response containing a list of branch names or an error message if the repository is not found. + - 200: A JSON object with a "status" key containing "deleted" + - 404: A JSON object with an "error" key containing the message "Repository not found" or "Branch not found" + """ + + repo_path = os.path.join(REPOSITORIES_BASE_PATH, repo + ".git") + if not os.path.isdir(repo_path): + journal.send(f"Can't delete branch. Repository storage at {REPOSITORIES_BASE_PATH} not found", PRIORITY=journal.LOG_ERROR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + return {"error": "Repository not found"}, 404 + + git_repo = git.Repo(repo_path) + git_repo.git.config('--global', '--add', 'safe.directory', repo_path) + + + if not branch in git_repo.branches: + journal.send(f"Can't delete branch. Not found in repository {repo}", PRIORITY=journal.LOG_ERROR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + return jsonify({"error": "Branch not found"}), 404 + + git_repo.delete_head(branch) + journal.send(f"Branch {branch} deleted in repo {repo}", PRIORITY=journal.LOG_ERROR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + + return jsonify({"status": "deleted"}), 200 + + +@app.route("/ogrepository/v1/git/repositories//tags", methods=['GET']) +def git_list_tags(repo): + """ + Retrieve the list of tags for a given repository. + + Args: + repo (str): The name of the repository. + + Returns: + Response: A JSON response containing a list of tag names or an error message if the repository is not found. + - 200: A JSON object with a "branches" key containing a list of tag names. + - 404: A JSON object with an "error" key containing the message "Repository not found" if the repository does not exist. + """ + repo_path = os.path.join(REPOSITORIES_BASE_PATH, repo + ".git") + if not os.path.isdir(repo_path): + journal.send(f"Can't list repositories. Repository storage at {REPOSITORIES_BASE_PATH} not found", PRIORITY=journal.LOG_ERROR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + return jsonify({"error": "Repository not found"}), 404 + + git_repo = git.Repo(repo_path) + git_repo.git.config('--global', '--add', 'safe.directory', repo_path) + + + tags = [] + for tag in git_repo.tags: + tags = tags + [tag.name] + + journal.send(f"Returning {len(tags)} branches", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + return { + "tags": tags + } + + +@app.route("/ogrepository/v1/git/repositories//tags/", methods=['POST']) +def git_create_tag(repo, tag): + """Create a given tag in a given repository + + Args: + repo (str): The name of the repository. + + Returns: + Response: A JSON response containing a creation status + - 200: A JSON object with a "status" key containing "created" + - 404: A JSON object with an "error" key containing the message "Repository not found" + - 409: A JSON object with an "error" key containing the message "Tag already exists" + """ + + repo_path = os.path.join(REPOSITORIES_BASE_PATH, repo + ".git") + if not os.path.isdir(repo_path): + journal.send(f"Can't create tag. Repository storage at {REPOSITORIES_BASE_PATH} not found", PRIORITY=journal.LOG_ERROR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + return jsonify({"error": "Repository not found"}), 404 + + git_repo = git.Repo(repo_path) + git_repo.git.config('--global', '--add', 'safe.directory', repo_path) + + data = request.json + if data is None: + journal.send(f"Can't create tag. JSON post data missing", PRIORITY=journal.LOG_ERROR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + return jsonify({"error" : "Parameters missing"}), 400 + + if not "commit" in data: + journal.send(f"Can't create tag. Commit parameter missing", PRIORITY=journal.LOG_ERROR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + return jsonify({"error" : "commit parameter missing"}), 400 + + if tag in git_repo.tags: + journal.send(f"Can't create tag. Already found in repository {repo}", PRIORITY=journal.LOG_ERROR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + return jsonify({"error": "Tag already exists"}), 409 + + git_repo.create_tag(tag, ref = data["commit"]) + + journal.send(f"Tag {tag} created in repo {repo}", PRIORITY=journal.LOG_ERROR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + return jsonify({"status": "created"}), 200 + +@app.route("/ogrepository/v1/git/repositories//tags/", methods=['DELETE']) +def git_delete_tag(repo, tag): + """Delete a given tag in a given repository + + Args: + repo (str): The name of the repository. + + Returns: + Response: A JSON response containing a list of tag names or an error message if the repository is not found. + - 200: A JSON object with a "status" key containing "deleted" + - 404: A JSON object with an "error" key containing the message "Repository not found" or "Tag not found" + """ + + repo_path = os.path.join(REPOSITORIES_BASE_PATH, repo + ".git") + if not os.path.isdir(repo_path): + journal.send(f"Can't delete tag. Repository storage at {REPOSITORIES_BASE_PATH} not found", PRIORITY=journal.LOG_ERROR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + return {"error": "Repository not found"}, 404 + + git_repo = git.Repo(repo_path) + git_repo.git.config('--global', '--add', 'safe.directory', repo_path) + + + if not tag in git_repo.tags: + journal.send(f"Can't delete tag. Not found in repository {repo}", PRIORITY=journal.LOG_ERROR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + return jsonify({"error": "Tag not found"}), 404 + + git_repo.delete_head(tag) + journal.send(f"Tag {tag} deleted in repo {repo}", PRIORITY=journal.LOG_ERROR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + + return jsonify({"status": "deleted"}), 200 + + + + # -------------------------------------------------------------------------------------------- diff --git a/api/swagger.yaml b/api/swagger.yaml index a7a8ebc..a7ca255 100644 --- a/api/swagger.yaml +++ b/api/swagger.yaml @@ -4,7 +4,7 @@ info: version: "1.0" description: | --- - + # ----------------------------------------------------------------------------------------------------------- # Esto hace que el Swagger se ordene por los tags (apartados), de la forma especificada: @@ -18,7 +18,7 @@ tags: - name: "Transferencia entre Repositorios y Backup" - name: "Importar y Exportar Máquinas Virtuales" - name: "Varios" - + - name: "Git" # ----------------------------------------------------------------------------------------------------------- # Apartado "Estado de ogRepository" @@ -30,7 +30,7 @@ paths: summary: "Obtener Información de Estado de ogRepository" description: > Este endpoint ejecuta el script "**getRepoStatus.py**" y devuelve su salida en formato JSON, - incluyendo información sobre la CPU, memoria RAM, disco duro, servicios, y procesos específicos de ogRepository, e instalación de ogGit. + incluyendo información sobre la CPU, memoria RAM, disco duro, servicios, y procesos específicos de ogRepository, e instalación de ogGit. tags: - "Estado de ogRepository" responses: @@ -140,14 +140,14 @@ paths: # ----------------------------------------------------------------------------------------------------------- # Apartado "Información de Imágenes" # ----------------------------------------------------------------------------------------------------------- - + /ogrepository/v1/images: put: summary: "Actualizar Información del Repositorio" description: | Este endpoint actualiza la información de las imágenes almacenadas en el repositorio, reflejándola en los archivos "**repoinfo.json**" y "**trashinfo.json**". Utiliza el script "**updateRepoInfo.py**", que a su vez llama al script "**updateTrashInfo.py**", para actualizar también la información de la papelera. - + No hace falta que se le llame al crear o exportar una imagen, ya que lo llama el endpoint "**Crear archivos auxiliares**" (que sí debe ser llamado en esos casos). tags: - "Información de Imágenes" @@ -192,7 +192,7 @@ paths: get: summary: "Obtener Información de todas las Imágenes" description: | - Este endpoint ejecuta el script "**getRepoInfo.py**" con el parámetro "**all**", para devolver información de todas las imágenes almacenadas en el repositorio y en la papelera, que a su vez llama al script "**updateRepoInfo.py**", para actualizar previamente la información del repositorio. + Este endpoint ejecuta el script "**getRepoInfo.py**" con el parámetro "**all**", para devolver información de todas las imágenes almacenadas en el repositorio y en la papelera, que a su vez llama al script "**updateRepoInfo.py**", para actualizar previamente la información del repositorio. Devuelve detalles como el nombre de la imagen, tipo, nombre del cliente, clonador, compresor, sistema de archivos, tamaño de los datos, tamaño de la imagen, y hashes MD5. tags: - "Información de Imágenes" @@ -1257,7 +1257,7 @@ paths: # ----------------------------------------------------------------------------------------------------------- - #/ogrepository/v1/p2p: + #/ogrepository/v1/p2p: delete: summary: "Cancelar Transmisiones P2P" description: | @@ -1311,10 +1311,10 @@ paths: Este endpoint importa la imagen especificada desde un repositorio remoto al repositorio local (en el que se ejecuta el endpoint). Utiliza el script "**importImage.py**", que recibe como parámetros el nombre de la imagen, la IP o hostname del servidor remoto, y el usuario con el que conectar al servidor remoto. que a su vez llama al script "**updateRepoInfo.py**", para actualizar la información del repositorio. - + **NOTA**: Este endpoint es asíncrono, ya que puede tardar mucho tiempo, por lo que solo informa de que la imagen se está importando, y abre un proceso paralelo, que avisará a ogCore cuando finalice la tarea (llamando a un endpoint de ogCore). tags: - - "Transferencia entre Repositorios y Backup" + - "Transferencia entre Repositorios y Backup" parameters: - name: JSON in: body @@ -1436,7 +1436,7 @@ paths: * **ID_img** - Identificador de la imagen, correspondiente al contenido del archivo 'full.sum' * **repo_ip** - Dirección IP del servidor remoto * **user** - Usuario con el que conectar al servidor remoto - * **remote_path** - Ruta remota en la que copiar la imagen + * **remote_path** - Ruta remota en la que copiar la imagen schema: type: object properties: @@ -1457,7 +1457,7 @@ paths: example: "/home/opengnsys" responses: "200": - description: "Se está haciendo backup de la imagen." + description: "Se está haciendo backup de la imagen." schema: type: object properties: @@ -1532,20 +1532,20 @@ paths: post: summary: "Convertir Imagen Virtual a Imagen OpenGnsys" description: | - Este endpoint convierte la imagen virtual especificada como primer parámetro en una imagen "img" como las que se generan desde OpenGnsys, debiendo haberse copiado previamente en la ruta "opt/opengnsys/ogrepository/images_virtual". - Utiliza el script "**convertVMtoIMG.py**", que recibe como parámetros el nombre de la imagen virtual, y el sistema de archivos de la partición a clonar (en formato "blkid"). - Se puede comprobar todos los sistemas de archivos aceptados por "blkid" ejecutando el comando "blkid -k". - + Este endpoint convierte la imagen virtual especificada como primer parámetro en una imagen "img" como las que se generan desde OpenGnsys, debiendo haberse copiado previamente en la ruta "opt/opengnsys/ogrepository/images_virtual". + Utiliza el script "**convertVMtoIMG.py**", que recibe como parámetros el nombre de la imagen virtual, y el sistema de archivos de la partición a clonar (en formato "blkid"). + Se puede comprobar todos los sistemas de archivos aceptados por "blkid" ejecutando el comando "blkid -k". + **NOTA**: Este endpoint es asíncrono, ya que puede tardar mucho tiempo, por lo que solo informa de que la imagen virtual se está convirtiendo, y abre un proceso paralelo, que avisará a ogCore cuando finalice la tarea (llamando a un endpoint de ogCore). tags: - - "Importar y Exportar Máquinas Virtuales" + - "Importar y Exportar Máquinas Virtuales" parameters: - name: JSON in: body required: true description: | - * **virtual_image** - Nombre de la imagen virtual, con extensión - * **filesystem** - Sistema de archivos de la partición a clonar, en formato "blkid" + * **virtual_image** - Nombre de la imagen virtual, con extensión + * **filesystem** - Sistema de archivos de la partición a clonar, en formato "blkid" schema: type: object properties: @@ -1640,19 +1640,19 @@ paths: put: summary: "Convertir Imagen OpenGnsys a Imagen Virtual" description: | - Este endpoint convierte la imagen "img" especificada como primer parámetro en una imagen virtual con la extensión especificada como segundo parámetro ("vdi", "vmdk", etc), guardándola en la ruta "opt/opengnsys/ogrepository/images_virtual/export". - Utiliza el script "**convertIMGtoVM.py**", que recibe como parámetros el nombre de la imagen, y la extensión del disco virtual destino ("vdi", "vmdk", etc). - + Este endpoint convierte la imagen "img" especificada como primer parámetro en una imagen virtual con la extensión especificada como segundo parámetro ("vdi", "vmdk", etc), guardándola en la ruta "opt/opengnsys/ogrepository/images_virtual/export". + Utiliza el script "**convertIMGtoVM.py**", que recibe como parámetros el nombre de la imagen, y la extensión del disco virtual destino ("vdi", "vmdk", etc). + **NOTA**: Este endpoint es asíncrono, ya que puede tardar mucho tiempo, por lo que solo informa de que la imagen se está convirtiendo a virtual, y abre un proceso paralelo, que avisará a ogCore cuando finalice la tarea (llamando a un endpoint de ogCore). tags: - - "Importar y Exportar Máquinas Virtuales" + - "Importar y Exportar Máquinas Virtuales" parameters: - name: JSON in: body required: true description: | * **ID_img** - Identificador de la imagen, correspondiente al contenido del archivo 'full.sum' - * **vm_extension** - Extensión del disco virtual destino ("vdi", "vmdk", etc) + * **vm_extension** - Extensión del disco virtual destino ("vdi", "vmdk", etc) schema: type: object properties: @@ -1883,7 +1883,7 @@ paths: 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. + Utiliza el script "**renameImage.py**", que recibe como parámetros el nombre original y el nuevo nombre a asignar. tags: - "Varios" parameters: @@ -1960,3 +1960,299 @@ paths: example: "(Exception description)" # ----------------------------------------------------------------------------------------------------------- + + +# ----------------------------------------------------------- +# ____ _ _ +# / ___(_) |_ +# | | _| | __| +# | |_| | | |_ +# \____|_|\__| +# +# ----------------------------------------------------------- + + /ogrepository/v1/git/repositories: + get: + summary: "Obtener lista de repositorios" + description: | + Devuelve una lista de repositorios de Git + tags: + - "Git" + parameters: + responses: + "200": + description: "Lista de repositorios" + schema: + type: array + items: + type: string + example: linux + + "500": + description: "Excepción" + schema: + type: object + properties: + success: + type: boolean + example: false + exception: + type: string + example: "(Exception description)" + post: + summary: "Crear repositorio" + description: | + Crea un repositorio nuevo de Git + tags: + - "Git" + parameters: + - name: JSON + in: body + required: true + description: | + * **name** - Nombre de repositorio + schema: + type: object + properties: + name: + type: string + example: linux + responses: + "201": + description: "Repositorio creado" + schema: + type: object + properties: + status: + type: string + example: "Repository created" + "500 (Exception)": + description: "JSON post data missing" + schema: + type: object + properties: + error: + type: string + example: "Parameters missing" + /ogrepository/v1/git/tags: + get: + summary: "Obtener lista de tags" + description: | + Devuelve una lista de tags de Git + tags: + - "Git" + parameters: + responses: + "200": + description: "Lista de tags" + schema: + type: array + items: + type: string + example: v0.1 + + "500": + description: "Excepción" + schema: + type: object + properties: + success: + type: boolean + example: false + exception: + type: string + example: "(Exception description)" + /ogrepository/v1/git/tags/{branchId}: + post: + summary: "Crear tag" + description: | + Crea una tag de git + tags: + - "Git" + parameters: + - name: JSON + in: body + required: true + description: | + * **commit** - Commit al que apunta el tag nuevo. Puede ser un nombre de otra rama/tag. + schema: + type: object + properties: + name: + type: string + example: HEAD + responses: + "201": + description: "Tag creado" + schema: + type: object + properties: + status: + type: string + example: created + + "500": + description: "Excepción" + schema: + type: object + properties: + success: + type: boolean + example: false + exception: + type: string + example: "(Exception description)" + "404": + description: "El repositorio no existe" + schema: + type: object + properties: + error: + type: string + example: "Repository not found" + "409": + description: "El tag ya existe" + schema: + type: object + properties: + error: + type: string + example: "Tag already exists" + delete: + summary: "Eliminar tag" + description: | + Elimina un tag de git + tags: + - "Git" + parameters: + responses: + "200": + description: "Tag eliminado" + schema: + type: object + properties: + status: + type: string + example: deleted + "500": + description: "Git no instalado" + schema: + type: object + properties: + success: + type: boolean + example: false + exception: + type: string + example: "(Exception description)" + /ogrepository/v1/git/branches: + get: + summary: "Obtener lista de branches" + description: | + Devuelve una lista de branches de Git + tags: + - "Git" + parameters: + responses: + "200": + description: "Lista de branches" + schema: + type: array + items: + type: string + example: main + + "500": + description: "Excepción" + schema: + type: object + properties: + success: + type: boolean + example: false + exception: + type: string + example: "(Exception description)" + "404": + description: "El repositorio no existe" + schema: + type: object + properties: + error: + type: string + example: "Repository not found" + "409": + description: "El branch ya existe" + schema: + type: object + properties: + error: + type: string + example: "Branch already exists" + + /ogrepository/v1/git/branches/{branchId}: + post: + summary: "Crear branch" + description: | + Crea una rama de git + tags: + - "Git" + parameters: + - name: JSON + in: body + required: true + description: | + * **commit** - Commit al que apunta la rama nueva. Puede ser un nombre de otra rama/tag. + schema: + type: object + properties: + name: + type: string + example: HEAD + responses: + "201": + description: "Rama creada" + schema: + type: array + items: + type: string + example: main + + "500": + description: "Git no instalado" + schema: + type: object + properties: + success: + type: boolean + example: false + exception: + type: string + example: "(Exception description)" + delete: + summary: "Eliminar branch" + description: | + Elimina una rama de git + tags: + - "Git" + parameters: + responses: + "200": + description: "Branch eliminado" + schema: + type: object + properties: + status: + type: string + example: deleted + "500": + description: "Excepción" + schema: + type: object + properties: + success: + type: boolean + example: false + exception: + type: string + example: "(Exception description)" \ No newline at end of file