diff --git a/api_server/README.md b/api_server/README.md deleted file mode 100644 index d2c1a81..0000000 --- a/api_server/README.md +++ /dev/null @@ -1,42 +0,0 @@ -# API Server - -`api_server.py` is a Flask script that loads and executes Flask blueprints from the `blueprints` directory. - - -Currently it's intended to combine oggit and ogrepository. - - -# Usage - -## Ubuntu 24.04 - - sudo apt install -y python3-flask python3-paramiko opengnsys-flask-executor opengnsys-flask-restx - -The `opengnsys-flask-executor` and `opengnsys-flask-restx` packages are available on the OpenGnsys package server. - -Run with: - - ./api_server.py - - -# Operation - -## Requirements - -The gitapi is designed to run within an existing opengnsys environment. It should be installed in an ogrepository. - - -## API Examples - -### Get list of branches - - $ curl -L http://localhost:5000/repositories/linux/branches - { - "branches": [ - "master" - ] - } - -### Synchronize with remote repository - - curl --header "Content-Type: application/json" --data '{"remote_repository":"foobar"}' -X POST -L http://localhost:5000/repositories/linux/sync \ No newline at end of file diff --git a/api_server/api_server.py b/api_server/api_server.py deleted file mode 100755 index 1e33b10..0000000 --- a/api_server/api_server.py +++ /dev/null @@ -1,170 +0,0 @@ -#!/usr/bin/env python3 -import os -import sys - -sys.path.insert(0, "/usr/share/opengnsys-modules/python3/dist-packages") -sys.path.insert(0, "/opt/opengnsys/oggit/bin/") - - -import importlib -import logging -import uuid -import argparse -import yaml -from flask import Flask, request -from flask_executor import Executor -from flask_restx import Api -from flasgger import Swagger -from werkzeug.exceptions import HTTPException -from systemd.journal import JournalHandler - -class FakeArgs: - def __init__(self): - self.verbose = False - self.listen = None - self.port = None - self.debug = None - - -parser = argparse.ArgumentParser( - prog="api_server.py", - description="OpenGnsys Repository API Server", -) - -debug_enabled = False -listen_host = '0.0.0.0' -listen_port = 8006 - -is_gunicorn = "gunicorn" in os.environ.get("SERVER_SOFTWARE", "") - -if not is_gunicorn: - # Gunicorn passes us all the arguments passed to gunicorn itself, which of course crashes here since we don't recognize them. - # Deal with this by not doing argument handling when running under gunicorn - parser.add_argument('--debug', action='store_true', help="Enable debug output") - parser.add_argument('--listen', metavar="HOST", help="Listen address") - parser.add_argument('--port', metavar="PORT", help="Listen port") - parser.add_argument("-v", "--verbose", action="store_true", help = "Verbose console output") - - args = parser.parse_args() -else: - args = FakeArgs() - - -log = logging.getLogger('api_server') -log.addHandler(JournalHandler()) - -if args.verbose: - log.addHandler(logging.StreamHandler(stream=sys.stderr)) - log.setLevel(logging.DEBUG) -else: - log.setLevel(logging.INFO) - - -if is_gunicorn: - log.info("Running under gunicorn, argument handling disabled.") - -if args.listen: - listen_host = args.listen - -if args.port: - listen_port = args.port - - -if args.debug: - debug_enabled = True - - -api_base_dir = os.path.dirname(os.path.realpath(__file__)) -blueprints_dir = os.path.join(api_base_dir, 'blueprints') -installer_dir = os.path.join(api_base_dir, '../installer') - - - - -sys.path.insert(0, installer_dir) - - - - - -# Create an instance of the Flask class -app = Flask(__name__) -api = Api(app, - version='0.50', - title = "OpenGnsys Git API", - description = "API for managing disk images stored in Git", - doc = "/apidocs/") - - -executor = Executor(app) - -log.info("Loading blueprints from %s", blueprints_dir) -sys.path.insert(0, blueprints_dir) - -for filename in os.listdir(blueprints_dir): - if filename.endswith('.py'): - - log.info("Loading %s/%s", blueprints_dir, filename) - - module_name = filename.replace(".py", "") - swagger_file = os.path.join(blueprints_dir, filename.replace(".py", ".yaml")) - - log.info("Importing %s", module_name) - importlib.invalidate_caches() - module = importlib.import_module(module_name) - log.debug("Returned: %s", module) - - app.register_blueprint(module.blueprint) - - if os.path.exists(swagger_file): - log.info("Loading Swagger documentation from %s...", swagger_file) - - with open(swagger_file, "r", encoding='utf-8') as file: - swagger_template = yaml.safe_load(file) - - #print(f"Template: {swagger_template}") - #swagger = Swagger(app, template=swagger_template) - else: - log.warning("Swagger not found for this module, looked in %s", swagger_file) - - - -@app.errorhandler(HTTPException) -def handle_exception(e): - """Return JSON for HTTP errors. - - We create and log an error UUID for each error, and use journald's additional fields for easier searching. - """ - # start with the correct headers and status code from the error - response = e.get_response() - - errid = uuid.uuid4().hex - - - response = { - "errcode": e.code, - "errname": e.name, - "description": e.description, - } - - log.error("Error ID %s: code %i, name %s, description %s", errid, e.code, e.name, e.description, extra = { "error_id" : errid, "errcode" : e.code, "errname" : e.name, "description" : e.description }) - - return response, 500 - -@app.after_request -def after_request(response): - log.info("Request from %s: %s %s %s %s", request.remote_addr, request.method, request.scheme, request.full_path, response.status, - extra = {"remote_addr" : request.remote_addr, "method" : request.method, "scheme" : request.scheme, "full_path" : request.full_path, "status" : response.status}) - - if debug_enabled: - log.debug("Response: %s", response.data, extra = {"response" : response.data}) - - return response - - - - -# Run the Flask app -if __name__ == '__main__': - print(f"Map: {app.url_map}") - app.run(debug=debug_enabled, host=listen_host, port=listen_port) diff --git a/api_server/blueprints/gitapi.py b/api_server/blueprints/gitapi.py deleted file mode 100755 index 888781a..0000000 --- a/api_server/blueprints/gitapi.py +++ /dev/null @@ -1,713 +0,0 @@ -#!/usr/bin/env python3 -""" -This module provides a Flask-based API for managing Git repositories in the OpenGnsys system. -It includes endpoints for creating, deleting, synchronizing, backing up, and performing garbage -collection on Git repositories. The API also provides endpoints for retrieving repository -information such as the list of repositories and branches, as well as checking the status of -asynchronous tasks. - -Classes: - None - -Functions: - do_repo_backup(repo, params) - - do_repo_sync(repo, params) - - do_repo_gc(repo) - - home() - - get_repositories() - - create_repo(repo) - - sync_repo(repo) - - backup_repository(repo) - - gc_repo(repo) - - tasks_status(task_id) - - delete_repo(repo) - - get_repository_branches(repo) - - health_check() - -Constants: - REPOSITORIES_BASE_PATH (str): The base path where Git repositories are stored. - -Global Variables: - app (Flask): The Flask application instance. - executor (Executor): The Flask-Executor instance for managing asynchronous tasks. - tasks (dict): A dictionary to store the status of asynchronous tasks. -""" - -# pylint: disable=locally-disabled, line-too-long - -import os.path -import os -import shutil -import uuid -import time -import logging -import traceback - -import git -from opengnsys_git_installer import OpengnsysGitInstaller -from flask import Blueprint, request -from flask_restx import Resource, Api -import paramiko -from systemd.journal import JournalHandler - - -debug_enabled = False - -log = logging.getLogger('gitapi') -log.addHandler(JournalHandler()) -log.setLevel(logging.INFO) -log.info("Started") - - -REPOSITORIES_BASE_PATH = "/opt/opengnsys/ogrepository/oggit/git/oggit/" - -start_time = time.time() -tasks = {} -tasks_max = 1024 - -blueprint = Blueprint('git_api', __name__, template_folder='templates', url_prefix = '/oggit/v1') -api = Api(blueprint) -git_ns = api - - -def add_task(future): - task_id = uuid.uuid4().hex - task_data = { - "future" : future, - "start_time" : time.time() - } - - while len(tasks) >= tasks_max: - oldest_task_id = min(tasks, key=lambda k: tasks[k]['start_time']) - task = tasks[task_id]["future"] - if task.running(): - log.error("Cancelling still running task %s, maximum task limit of %i reached", task_id, tasks_max) - task.cancel() - - del tasks[oldest_task_id] - - tasks[task_id] = task_data - return task_id - -def do_repo_backup(repo, params): - """ - Creates a backup of the specified Git repository and uploads it to a remote server via SFTP. - - Args: - repo (str): The name of the repository to back up. - params (dict): A dictionary containing the following keys: - - ssh_server (str): The SSH server address. - - ssh_port (int): The SSH server port. - - ssh_user (str): The SSH username. - - filename (str): The remote filename where the backup will be stored. - - Returns: - bool: True if the backup was successful. - """ - - git_repo_path = f"{REPOSITORIES_BASE_PATH}/{repo}.git" - git_repo = git.Repo(git_repo_path) - git_repo.git.config('--global', '--add', 'safe.directory', git_repo_path) - - - ssh = paramiko.SSHClient() - ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) - - ssh.connect(params["ssh_server"], params["ssh_port"], params["ssh_user"]) - sftp = ssh.open_sftp() - - - with sftp.file(params["filename"], mode='wb+') as remote_file: - git_repo.archive(remote_file, format="tar.gz") - - - return True - -def do_repo_sync(repo, params): - """ - Synchronizes a local Git repository with a remote repository. - - Args: - repo (str): The name of the local repository to synchronize. - params (dict): A dictionary containing the remote repository URL with the key "remote_repository". - - Returns: - list: A list of dictionaries, each containing: - - "local_ref" (str): The name of the local reference. - - "remote_ref" (str): The name of the remote reference. - - "summary" (str): A summary of the push operation for the reference. - """ - git_repo_path = f"{REPOSITORIES_BASE_PATH}/{repo}.git" - git_repo = git.Repo(git_repo_path) - git_repo.git.config('--global', '--add', 'safe.directory', git_repo_path) - - - # Recreate the remote every time, it might change - if "backup" in git_repo.remotes: - git_repo.delete_remote("backup") - - backup_repo = git_repo.create_remote("backup", params["remote_repository"]) - pushed_references = backup_repo.push("*:*") - results = [] - - # This gets returned to the API - for ref in pushed_references: - results = results + [ {"local_ref" : ref.local_ref.name, "remote_ref" : ref.remote_ref.name, "summary" : ref.summary }] - - return results - -def do_repo_gc(repo): - """ - Perform garbage collection on the specified Git repository. - - Args: - repo (str): The name of the repository to perform garbage collection on. - - Returns: - bool: True if the garbage collection command was executed successfully. - """ - git_repo_path = os.path.join(REPOSITORIES_BASE_PATH, repo + ".git") - git_repo = git.Repo(git_repo_path) - git_repo.git.config('--global', '--add', 'safe.directory', git_repo_path) - - git_repo.git.gc() - - - -# Define a route for the root URL -@api.route('/') -class GitLib(Resource): - - #@api.doc('home') - def get(self): - """ - Home route that returns a JSON response with a welcome message for the OpenGnsys Git API. - - Returns: - Response: A Flask JSON response containing a welcome message. - """ - log.info("Root URL accessed") - - return { - "message": "OpenGnsys Git API" - } - -@git_ns.route('/repositories') -class GitRepositories(Resource): - def get(self): - """ - 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"] - } - """ - - if not os.path.isdir(REPOSITORIES_BASE_PATH): - log.error("Can't list repositories. Repository storage at %s not found", REPOSITORIES_BASE_PATH, extra = {"path" : REPOSITORIES_BASE_PATH}) - return {"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] - - log.info("Returning %i repositories", len(repos)) - return { - "repositories": repos - } - - def post(self): - """ - 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: - log.error("Can't create repository, JSON post data missing") - return {"error" : "Parameters missing"}, 400 - - repo = data["name"] - - repo_path = os.path.join(REPOSITORIES_BASE_PATH, repo + ".git") - if os.path.isdir(repo_path): - log.error("Can't create repository %s, already exists at %s", repo, repo_path, extra = {"repository" : repo, "path" : repo_path}) - return {"status": "Repository already exists"}, 200 - - - installer = OpengnsysGitInstaller() - installer.add_forgejo_repo(repo) - - #installer.init_git_repo(repo + ".git") - - log.info("Repository %s created", repo, extra = {"repository" : repo}) - return {"status": "Repository created"}, 201 - - -@git_ns.route('/repositories//sync') -class GitRepoSync(Resource): - def post(self, repo): - """ - Synchronize a repository with a remote repository. - - This endpoint triggers the synchronization process for a specified repository. - It expects a JSON payload with the remote repository details. - - Args: - repo (str): The name of the repository to be synchronized. - - Returns: - Response: A JSON response indicating the status of the synchronization process. - - 200: If the synchronization process has started successfully. - - 400: If the request payload is missing or invalid. - - 404: If the specified repository is not found. - """ - repo_path = os.path.join(REPOSITORIES_BASE_PATH, repo + ".git") - if not os.path.isdir(repo_path): - log.error("Can't sync repository %s, not found. Looked in %s", repo, repo_path, extra = {"repository" : repo, "path" : repo_path }) - return {"error": "Repository not found"}, 404 - - - data = request.json - - if data is None: - log.error("Can't create repository, JSON post data missing") - return {"error" : "Parameters missing"}, 400 - - if not "remote_repository" in data: - log.error("Can't create repository, parameter 'remote_repository' missing") - return {"error" : "Parameter 'remote_repository' missing"}, 400 - - - future = executor.submit(do_repo_sync, repo, data) - task_id = add_task(future) - - log.info("Starting synchronization of repository %s, task %s", repo, task_id, extra = {"repository" : repo, "task_id" : task_id}) - return {"status": "started", "task_id" : task_id}, 200 - - - -@git_ns.route('/repositories//backup') -class GitRepoBackup(Resource): - def backup_repository(self, repo): - """ - Backup a specified repository. - - Endpoint: POST /repositories//backup - - Args: - repo (str): The name of the repository to back up. - - Request Body (JSON): - ssh_port (int, optional): The SSH port to use for the backup. Defaults to 22. - - Returns: - Response: A JSON response indicating the status of the backup operation. - - If the repository is not found, returns a 404 error with a message. - - If the request body is missing, returns a 400 error with a message. - - If the backup process starts successfully, returns a 200 status with the task ID. - - Notes: - - The repository path is constructed by appending ".git" to the repository name. - - The backup operation is performed asynchronously using a thread pool executor. - - The task ID of the backup operation is generated using UUID and stored in a global tasks dictionary. - """ - repo_path = os.path.join(REPOSITORIES_BASE_PATH, repo + ".git") - if not os.path.isdir(repo_path): - log.error("Can't backup repository %s, not found. Looked in %s", repo, repo_path, extra = {"repository" : repo, "path" : repo_path }) - return {"error": "Repository not found"}, 404 - - - data = request.json - if data is None: - log.error("Can't create repository, JSON post data missing") - return {"error" : "Parameters missing"}, 400 - - - if not "ssh_port" in data: - data["ssh_port"] = 22 - - - future = executor.submit(do_repo_backup, repo, data) - task_id = add_task(future) - - log.info("Starting backup of repository %s, task %s", repo, task_id, extra = {"repository" : repo, "task_id" : task_id}) - return {"status": "started", "task_id" : task_id}, 200 - -@git_ns.route('/repositories//compact', methods=['POST']) -class GitRepoCompact(Resource): - def post(self, repo): - """ - Initiates a garbage collection (GC) process for a specified Git repository. - - This endpoint triggers an asynchronous GC task for the given repository. - The task is submitted to an executor, and a unique task ID is generated - and returned to the client. - - Args: - repo (str): The name of the repository to perform GC on. - - Returns: - Response: A JSON response containing the status of the request and - a unique task ID if the repository is found, or an error - message if the repository is not found. - """ - repo_path = os.path.join(REPOSITORIES_BASE_PATH, repo + ".git") - if not os.path.isdir(repo_path): - log.error("Can't compact repository %s, not found. Looked in %s", repo, repo_path, extra = {"repository" : repo, "path" : repo_path }) - return {"error": "Repository not found"}, 404 - - future = executor.submit(do_repo_gc, repo) - task_id = add_task(future) - - log.info("Starting compaction of repository %s, task %s", repo, task_id, extra = {"repository" : repo, "task_id" : task_id}) - return {"status": "started", "task_id" : task_id}, 200 - - -@git_ns.route('/tasks//status') -class GitTaskStatus(Resource): - def get(self, task_id): - """ - Endpoint to check the status of a specific task. - - Args: - task_id (str): The unique identifier of the task. - - Returns: - Response: A JSON response containing the status of the task. - - If the task is not found, returns a 404 error with an error message. - - If the task is completed, returns a 200 status with the result. - - If the task is still in progress, returns a 202 status indicating the task is in progress. - """ - if not task_id in tasks: - log.error("Task %s was not found", task_id, extra = {"task_id" : task_id}) - return {"error": "Task not found"}, 404 - - future = tasks[task_id]["future"] - - try: - if future.done(): - result = future.result() - log.info("Returning completion of task %s", task_id, extra = {"task_id" : task_id}) - return {"status" : "completed", "result" : result}, 200 - else: - log.info("Task %s is still in progress", task_id, extra = {"task_id" : task_id}) - return {"status" : "in progress"}, 202 - except Exception as e: - errid = uuid.uuid4().hex - - - log.error("Task %s failed with exception %s, UUID %s", task_id, traceback.format_exception(e), errid, extra = {"task_id" : task_id, "exception" : traceback.format_exception(e), "error_id" : errid}) - return {"status" : "internal error", "error_id" : errid }, 500 - - -@git_ns.route('/repositories/', methods=['DELETE']) -class GitRepo(Resource): - def delete(self, repo): - """ - Deletes a Git repository. - - This endpoint deletes a Git repository specified by the `repo` parameter. - If the repository does not exist, it returns a 404 error with a message - indicating that the repository was not found. If the repository is successfully - deleted, it returns a 200 status with a message indicating that the repository - was deleted. - - Args: - repo (str): The name of the repository to delete. - - Returns: - Response: A JSON response with a status message and the appropriate HTTP status code. - """ - repo_path = os.path.join(REPOSITORIES_BASE_PATH, repo + ".git") - if not os.path.isdir(repo_path): - log.error("Can't delete repository %s, not found. Looked in %s", repo, repo_path, extra = {"repository" : repo, "path" : repo_path }) - return {"error": "Repository not found"}, 404 - - - shutil.rmtree(repo_path) - log.info("Deleted repository %s", repo, extra = {"repository" : repo}) - return {"status": "Repository deleted"}, 200 - - - - -@git_ns.route('/repositories//branches') -class GitRepoBranches(Resource): - def get(self, 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): - log.error("Can't get branches of repository repository %s, not found. Looked in %s", repo, repo_path, extra = {"repository" : repo, "path" : repo_path }) - return {"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] - - log.info("Returning %i branches", len(branches)) - return { - "branches": branches - } - -@git_ns.route('/repositories//branches/') -class GitRepoBranchesDeleter(Resource): - def delete(self, 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): - log.error("Can't get branches of repository repository %s, not found. Looked in %s", repo, repo_path, extra = {"repository" : repo, "path" : repo_path }) - 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: - log.error("Can't delete branch %s, not found in repository %s", branch, repo, extra = {"repository" : repo, "branch" : branch}) - return {"error": "Branch not found"}, 404 - - git_repo.delete_head(branch) - log.info("Branch %s of repository %s deleted", branch, repo, extra = {"repository" : repo, "branch" : branch}) - return {"status": "deleted"}, 200 - - def post(self, 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 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" - - 409: A JSON object with an "error" key containing the message "Branch already exists" - """ - - repo_path = os.path.join(REPOSITORIES_BASE_PATH, repo + ".git") - if not os.path.isdir(repo_path): - log.error("Can't get branches of repository repository %s, not found. Looked in %s", repo, repo_path, extra = {"repository" : repo, "path" : repo_path }) - return {"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: - log.error("Can't create branch, JSON post data missing") - return {"error" : "Parameters missing"}, 400 - - if not "commit" in data: - log.error("Can't create branch, commit parameter missing") - return {"error" : "commit parameter missing"}, 400 - - - if branch in git_repo.branches: - log.error("Can't create branch %s, already found in repository %s", branch, repo, extra = {"repository" : repo, "branch" : branch}) - return {"error": "Branch already exists"}, 409 - - git_repo.create_head(branch, commit = data["commit"] ) - log.info("Branch %s of repository %s created", branch, repo, extra = {"repository" : repo, "branch" : branch}) - return {"status": "created"}, 200 - -@git_ns.route('/repositories//tags') -class GitRepoTags(Resource): - def get(self, 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 tags names or an error message if the repository is not found. - - 200: A JSON object with a "tags" key containing a list of tags 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): - log.error("Can't get tags of repository repository %s, not found. Looked in %s", repo, repo_path, extra = {"repository" : repo, "path" : repo_path }) - return {"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] - - log.info("Returning %i tags", len(tags)) - return { - "tags": tags - } - -@git_ns.route('/repositories//tags/') -class GitRepoTagsDeleter(Resource): - def delete(self, 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): - log.error("Can't get tags of repository repository %s, not found. Looked in %s", repo, repo_path, extra = {"repository" : repo, "path" : repo_path }) - 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: - log.error("Can't delete tag %s, not found in repository %s", tag, repo, extra = {"repository" : repo, "tag" : tag}) - return {"error": "Tag not found"}, 404 - - git_repo.delete_head(tag) - log.info("Tag %s of repository %s deleted", tag, repo, extra = {"repository" : repo, "tag" : tag}) - return {"status": "deleted"}, 200 - - def post(self, 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): - log.error("Can't get tags of repository repository %s, not found. Looked in %s", repo, repo_path, extra = {"repository" : repo, "path" : repo_path }) - return {"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: - log.error("Can't create tag, JSON post data missing") - return {"error" : "Parameters missing"}, 400 - - if not "commit" in data: - log.error("Can't create tag, commit parameter missing") - return {"error" : "commit parameter missing"}, 400 - - - if tag in git_repo.tags: - log.error("Can't create tag %s, already found in repository %s", tag, repo, extra = {"repository" : repo, "tag" : tag}) - return {"error": "Tag already exists"}, 409 - git_repo.create_tag(tag, ref = data["commit"]) - - log.info("Tag %s of repository %s created", tag, repo, extra = {"repository" : repo, "tag" : tag}) - return {"status": "created"}, 200 - -@git_ns.route('/health') -class GitHealth(Resource): - def get(self): - """ - Health check endpoint. - - This endpoint returns a JSON response indicating the health status of the application. - - Returns: - Response: A JSON response with a status key set to "OK". Currently it always returns - a successful value, but this endpoint can still be used to check that the API is - active and functional. - - """ - log.info("Health check endpoint called") - return { - "status": "OK" - } - -@git_ns.route('/status') -class GitStatus(Resource): - def get(self): - """ - Status check endpoint. - - This endpoint returns a JSON response indicating the status of the application. - - Returns: - Response: A JSON response with status information - - """ - log.info("Status endpoint called") - - return { - "uptime" : time.time() - start_time, - "active_tasks" : len(tasks) - } diff --git a/api_server/blueprints/repo_api.py b/api_server/blueprints/repo_api.py deleted file mode 100755 index 629b72e..0000000 --- a/api_server/blueprints/repo_api.py +++ /dev/null @@ -1,2099 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -""" - 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). - -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") -""" - -# -------------------------------------------------------------------------------------------- -# IMPORTS -# -------------------------------------------------------------------------------------------- - -from flask import Flask, jsonify, request, Blueprint -import os -import subprocess -import json -from time import sleep -import paramiko -import logging -import threading -import requests -import random -import hashlib -import psutil -from systemd import journal -# Imports para Swagger: -from flasgger import Swagger -import yaml - - -# -------------------------------------------------------------------------------------------- -# VARIABLES -# -------------------------------------------------------------------------------------------- - -repo_path = '/opt/opengnsys/ogrepository/images/' # No borrar la barra final -vm_path = '/opt/opengnsys/ogrepository/images_virtual/' # No borrar la barra final -script_path = '/opt/opengnsys/ogrepository/bin' -repo_file = '/opt/opengnsys/ogrepository/etc/repoinfo.json' -trash_file = '/opt/opengnsys/ogrepository/etc/trashinfo.json' -config_file = '/opt/opengnsys/ogrepository/etc/ogAdmRepo.cfg' - - -# -------------------------------------------------------------------------------------------- -# FUNCTIONS -# -------------------------------------------------------------------------------------------- - -blueprint = Blueprint('repo_api', __name__, template_folder='templates') -app = blueprint - -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: - if line.startswith('IPcore'): - IPcore = line.split('=')[1].strip() - journal.send(f"ogCore IP obtained ({IPcore})", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - journal.send(f"{{'component':'ogRepo', 'severity':'INFO', 'operation':'Run function get_IPcore', 'desc':'ogCore IP obtained ({IPcore})'}}", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api") - return IPcore - if IPcore is None: - journal.send("Can't obtain ogCore IP", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - journal.send("{'component':'ogRepo', 'severity':'ERROR', 'operation':'Run function get_IPcore', 'desc':'Unable to obtain ogCore IP'}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api") - return "IP no encontrada en el archivo de configuración" - - -# --------------------------------------------------------- - - -def get_image_params(image_id, search='all'): - """ A partir de un ID de imagen (que corresponde al "fullsum"), busca la imagen en el repositorio y/o en la papelera (dependiendo del parámetro "search"). - Si encuentra la imagen devuelve su nombre y su extensión en un diccionario, y si no encuentra la imagen especificada retorna "None". - El parámtro "search" tiene el valor predeterminado "all" (que hará que busque tanto en el repo como en la papelera), - pero se le puede pasar el valor "repo" (para que busque solo en el repo) o "trash" (para que busque solo en la papelera). - """ - journal.send("Running function 'get_image_params'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - - # Creamos un diccionario vacío, para almacenar los resultados: - result = {} - - # Abrimos y almacenamos el archivo "repoinfo.json" (solo si se ha de buscar en el repo, y si el archivo tiene contenido): - if (search == 'all' or search == 'repo') and os.path.getsize(repo_file) > 0: - with open(repo_file, 'r') as file: - repo_data = json.load(file) - # Iteramos la clave "images" y buscamos la imagen (y si la encontramos almacenamos el nombre y la extension): - for image in repo_data.get('images', []): - if image.get('fullsum') == image_id: - result['name'] = image.get('name') - result['extension'] = image.get('type') - journal.send("Image found in repository JSON file", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - journal.send("{'component':'ogRepo', 'severity':'INFO', 'operation':'Run function get_image_params', 'desc':'Image found in repository JSON file'}", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api") - return result - - # Abrimos y almacenamos el archivo "trashinfo.json" (solo si se ha de buscar en la papelera, y si el archivo tiene contenido): - if (search == 'all' or search == 'trash') and os.path.getsize(trash_file) > 0: - with open(trash_file, 'r') as file: - trash_data = json.load(file) - # Iteramos la clave "images" y buscamos la imagen (y si la encontramos almacenamos el nombre y la extension): - for image in trash_data.get('images', []): - if image.get('fullsum') == image_id: - result['name'] = image.get('name') - result['extension'] = image.get('type') - journal.send("Image found in trash JSON file", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - journal.send("{'component':'ogRepo', 'severity':'INFO', 'operation':'Run function get_image_params', 'desc':'Image found in trash JSON file'}", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api") - return result - - # Si no encontramos la imagen, retornamos "None": - journal.send("Image not found in JSON file", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - journal.send("{'component':'ogRepo', 'severity':'WARNING', 'operation':'Run function get_image_params', 'desc':'Image not found in JSON file'}", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api") - return None - - -# --------------------------------------------------------- - - -def search_process(process, string_to_search): - """ Busca procesos que contengan el valor del parámetro "process" y el valor del parámetro "string_to_search" (la ruta de la imagen, normalmente). - Si encuentra alguno retorna "True", y si no encuentra ninguno retorna "False". - """ - journal.send("Running function 'search_process'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - - try: - # Obtenemos todos los procesos que están corriendo, y almacenamos la salida y los errores: - result = subprocess.Popen(['ps', '-aux'], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding='UTF8') - out, error = result.communicate() - - # Almacenamos en una lista los procesos que contengan el proceso del parámetro y la cadena a buscar: - process_list = [line for line in out.split('\n') if process in line and string_to_search in line] - - # Si hemos encontrado algún proceso que cumpla las condiciones, retornamos "True", y si no retornamos "False": - if process_list != []: - journal.send("Process found", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - journal.send("{'component':'ogRepo', 'severity':'INFO', 'operation':'Run function search_process', 'desc':'Process found'}", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api") - return True - else: - journal.send("Process not found", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - journal.send("{'component':'ogRepo', 'severity':'WARNING', 'operation':'Run function search_process', 'desc':'Process not found'}", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api") - return False - # Si se ha producido una excepción, imprimimos el error: - except Exception as error_description: - journal.send(f"Function 'search_process' result KO (Exception: {str(error_description)})", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - journal.send(f"{{'component':'ogRepo', 'severity':'ERROR', 'operation':'Run function search_process', 'desc':'Result KO (Exception: {str(error_description)})'}}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api") - print(f"Unexpected error: {error_description}") - - -# --------------------------------------------------------- - - -def check_remote_connection(remote_ip, remote_user): - """ Comprueba la conexión SSH/SFTP con el servidor remoto que recibe como primer parámetro. - Se utiliza para chequear la conexión antes de importar o exportar una imagen. - """ - journal.send("Running function 'check_remote_connection'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - - try: - # 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()) - - # Conectamos con el equipo remoto por SSH (con claves): - ssh_client.connect(hostname=remote_ip, port=22, username=remote_user, passphrase='') - - # Iniciamos un cliente SFTP: - sftp_client = ssh_client.open_sftp() - - # Cerramos el cliente SSH y el cliente SFTP: - ssh_client.close() - sftp_client.close() - - # Retornamos "True", porque hemos conseguido conectar: - journal.send("Connection OK", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - journal.send("{'component':'ogRepo', 'severity':'INFO', 'operation':'Run function check_remote_connection', 'desc':'Connection OK'}", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api") - return True - - # Si se produce una excepción, retornamos "False": - except Exception as error_description: - journal.send(f"Function 'check_remote_connection' result KO (Exception: {str(error_description)})", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - journal.send(f"{{'component':'ogRepo', 'severity':'ERROR', 'operation':'Run function check_remote_connection', 'desc':'Result KO (Exception: {str(error_description)})'}}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api") - return False - - -# --------------------------------------------------------- - - -def check_remote_image(remote_ip, remote_user, image_file_path): - """ Conecta con el servidor remoto que recibe como primer parámetro, - para comprobar si la imagen que recibe como tercer parámetro existe, o si está bloqueada. - Se utiliza para chequear la imagen antes de importarla. - """ - journal.send("Running function 'check_remote_image'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - - # 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()) - - # Conectamos con el equipo remoto por SSH (con claves): - ssh_client.connect(hostname=remote_ip, port=22, username=remote_user, passphrase='') - - # Iniciamos un cliente SFTP: - sftp_client = ssh_client.open_sftp() - - # Si la imagen no existe, retornamos el mensaje correspondiente: - try: - sftp_client.stat(image_file_path) - except IOError: - return "Remote image not found" - - # Si la imagen existe pero está bloqueada, retornamos el mensaje correspondiente: - try: - sftp_client.stat(f"{image_file_path}.lock") - return "Remote image is locked" - except IOError: - print("Remote image is not locked, as expected") - - # Cerramos el cliente SSH y el cliente SFTP: - ssh_client.close() - sftp_client.close() - - -# --------------------------------------------------------- - - -def check_lock_local(image_file_path, job_id): - """ Cada minuto comprueba si existe un archivo ".lock" asociado a la imagen que recibe como parámetro, y si existe la imagen y sus archivos asociados. - Cuando ya no exista el archivo ".lock" (pero si los demás archivos), le comunicará a ogCore que la importación ha sido exitosa, y saldrá de bucle. - Cuando ya no exista el archivo ".lock" (pero tampoco los demás), le comunicará a ogCore que la importación ha fallado, y saldrá de bucle. - Mientras no se cumpla ninguna de las condiciones anteriores, y aun exista el archivo ".lock", seguirá haciendo la comprobación (repitiendo el bucle cada minuto). - """ - journal.send("Running function 'check_lock_local'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - - # Esperamos 30 segundos, para dar tiempo a que se cree el archivo ".lock": - sleep(30) - - # Creamos un bucle infinito: - while True: - # Si ya no existe el archivo ".lock" (pero sí existen los demás), respondemos a ogCore (con "success: True") y salimos del bucle: - if not os.path.exists(f"{image_file_path}.lock") and os.path.exists(image_file_path) and os.path.exists(f"{image_file_path}.full.sum") and os.path.exists(f"{image_file_path}.info.checked") and os.path.exists(f"{image_file_path}.size") and os.path.exists(f"{image_file_path}.sum") and os.path.exists(f"{image_file_path}.torrent"): - journal.send("Task finalized (image unlocked)", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - journal.send("{'component':'ogRepo', 'severity':'INFO', 'operation':'Run function check_lock_local', 'desc':'Image unlocked'}", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api") - - # Almacenamos en un diccionario los datos a enviar a ogCore: - data = { - 'job_id': job_id, - 'success': True - } - # Llamamos al endpoint de ogCore, enviando los datos del diccionario: - journal.send(f"Calling function 'recall_ogcore' (JOB_ID: {job_id}, SUCCESS: True)", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - recall_ogcore(data) - break - # Si no existe el archivo ".lock" (pero tampoco los demás), respondemos a ogCore (con "success: False") y salimos del bucle: - elif not os.path.exists(f"{image_file_path}.lock") and not os.path.exists(image_file_path) and not os.path.exists(f"{image_file_path}.full.sum") and not os.path.exists(f"{image_file_path}.info") and not os.path.exists(f"{image_file_path}.size") and not os.path.exists(f"{image_file_path}.sum") and not os.path.exists(f"{image_file_path}.torrent"): - journal.send("Imported image didn't pass the Integrity Check", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - journal.send("{'component':'ogRepo', 'severity':'ERROR', 'operation':'Run function check_lock_local', 'desc':'Integrity check failed'}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api") - - # Almacenamos en un diccionario los datos a enviar a ogCore: - data = { - 'job_id': job_id, - 'success': False - } - # Llamamos al endpoint de ogCore, enviando los datos del diccionario: - journal.send(f"Calling function 'recall_ogcore' (JOB_ID: {job_id}, SUCCESS: False)", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - recall_ogcore(data) - break - # Si aun existe el archivo ".lock" (y no se han cumplido las condiciones anteriores), imprimimos un mensaje en la API: - elif os.path.exists(f"{image_file_path}.lock"): - journal.send("Task in process (.lock file exists)", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - - # Esperamos 1 minuto para volver a realizar la comprobación: - sleep(60) - - -# --------------------------------------------------------- - - -def check_remote_backup(image_name, remote_ip, remote_user, remote_path, job_id): - """ Cada minuto comprueba si se ha copiado la imagen que recibe como primer parámetro (y sus archivos asociados) al equipo remoto que recibe como segundo parámetro, - y mientras eso no suceda seguirá haciendo la comprobación (en un bucle "while" que se ejecuta cada minuto). - Una vez copiados todos los archivos, chequea la integridad de la imagen, comparando el contenido de los archivos ".sum" y ".size" con los valores reales (que vuelve a calcular). - Si la comprobación es correcta saldrá del bucle, y si es incorrecta saldrá del bucle y borrará los archivos que se hayan copiado al equipo remoto - (y en ambos casos le comunicará a ogCore el resultado, llamando a la función "recall_ogcore"). - """ - journal.send("Running function 'check_remote_backup'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - - # 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()) - # Conectamos con el equipo remoto por SSH (con claves): - ssh_client.connect(hostname=remote_ip, port=22, username=remote_user, passphrase='') - # Iniciamos un cliente SFTP: - sftp_client = ssh_client.open_sftp() - - # Esperamos 30 segundos antes de empezar a realizar la comprobación: - sleep(30) - - # Creamos una lista con las extensiones de los archivos asociados a la imagen (incluyendo ninguna extensión, que corresponde a la propia imagen): - extensions = ['', '.size', '.sum', '.full.sum', '.info'] - - # 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: - for ext in extensions: - sftp_client.stat(f"{remote_path}{image_name}{ext}") - all_files_copied = True - except IOError: - all_files_copied = False - - # Si se han copiado todos los archivos, comprobamos la integridad de la imagen: - if all_files_copied == True: - journal.send("All files copied", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - - try: - # Calculamos el tamaño del la imagen exportada ("size") - file_size = sftp_client.stat(f"{remote_path}{image_name}").st_size - - # Calculamos el hash MD5 del último MB de la imagen exportada ("sum"): - last_mb_offset = max(0, file_size - 1024 * 1024) - with sftp_client.file(f"{remote_path}{image_name}", 'rb') as file: - file.seek(last_mb_offset) - last_mb = file.read(1024 * 1024) - file_sum = hashlib.md5(last_mb).hexdigest() - - # Obtenemos el valor almacenado en el archivo ".size: - with sftp_client.file(f"{remote_path}{image_name}.size", 'r') as file: - stored_size = int(file.read().strip()) - - # Obtenemos el valor almacenado en el archivo ".sum: - with sftp_client.file(f"{remote_path}{image_name}.sum", 'r') as file: - stored_sum = file.read().strip().decode('utf-8') - - except Exception as error: - journal.send(f"Integrity check returned an error: {error}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - journal.send("{'component':'ogRepo', 'severity':'ERROR', 'operation':'Run function check_remote_backup', 'desc':'Integrity check returned an error'}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api") - - # Borramos los archivos copiados, porque no hemos podido chequear la integridad de la imagen: - remove_remote_files(sftp_client, remote_path, image_name, extensions) - - # Almacenamos en un diccionario los datos a enviar a ogCore: - data = { - 'job_id': job_id, - 'success': False - } - # Llamamos al endpoint de ogCore, enviando los datos del diccionario: - journal.send(f"Calling function 'recall_ogcore' (JOB_ID: {job_id}, SUCCESS: False)", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - recall_ogcore(data) - break - - # Comprobamos si los datos almacenados coinciden con los datos obtenidos (en cuyo caso el backup habrá sido correcto): - if file_sum == stored_sum and file_size == stored_size: - journal.send("Task finalized (backup complete)", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - journal.send("{'component':'ogRepo', 'severity':'INFO', 'operation':'Run function check_remote_backup', 'desc':'Backup complete'}", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api") - - # Almacenamos en un diccionario los datos a enviar a ogCore: - data = { - 'job_id': job_id, - 'success': True - } - # Llamamos al endpoint de ogCore, enviando los datos del diccionario: - journal.send(f"Calling function 'recall_ogcore' (JOB_ID: {job_id}, SUCCESS: True)", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - recall_ogcore(data) - break - else: - journal.send("Exported image didn't pass the Integrity Check", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - journal.send("{'component':'ogRepo', 'severity':'ERROR', 'operation':'Run function check_remote_backup', 'desc':'Integrity check failed'}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api") - - # Borramos los archivos copiados, porque el chequeo de la integridad de la imagen no ha sido exitoso: - remove_remote_files(sftp_client, remote_path, image_name, extensions) - - # Almacenamos en un diccionario los datos a enviar a ogCore: - data = { - 'job_id': job_id, - 'success': False - } - # Llamamos al endpoint de ogCore, enviando los datos del diccionario: - journal.send(f"Calling function 'recall_ogcore' (JOB_ID: {job_id}, SUCCESS: False)", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - recall_ogcore(data) - break - else: - journal.send("Task in process (backup incomplete)", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - - # Esperamos 1 minuto para volver a realizar la comprobación: - sleep(60) - - # Ya fuera del bucle, cerramos el cliente SSH y el cliente SFTP: - ssh_client.close() - sftp_client.close() - - -# --------------------------------------------------------- - - -def check_aux_files(image_file_path, job_id): - """ Cada 10 segundos comprueba si se han creado todos los archivos auxiliares de la imagen que recibe como parámetro, - en cuyo caso lo comunicará a ogCore, llamando a un endpoint, y dejará de realizar la comprobación. - También obtiene el valor del archivo ".full.sum" (que corresonde al ID), y se lo comunica a ogCore. - """ - journal.send("Running function 'check_aux_files'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - - # Creamos un bucle infinito: - while True: - # Si faltan archivos auxiliares por crear, imprimimos un mensaje en la API: - if not os.path.exists(f"{image_file_path}.size") or not os.path.exists(f"{image_file_path}.sum") or not os.path.exists(f"{image_file_path}.full.sum") or not os.path.exists(f"{image_file_path}.torrent") or not os.path.exists(f"{image_file_path}.info.checked"): - journal.send("Task in process (auxiliar files remaining)", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - # Si ya se han creado todos los archivos auxiliares, imprimimos un mensaje en la API, respondemos a ogCore y salimos del bucle: - else: - journal.send("Task finalized (all auxilar files created)", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - journal.send("{'component':'ogRepo', 'severity':'INFO', 'operation':'Run function check_aux_files', 'desc':'Auxiliar files created'}", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api") - - # Obtenemos el valor del archivo "full.sum", que corresponde al ID: - with open(f"{image_file_path}.full.sum", 'r') as file: - image_id = file.read().strip('\n') - - # Almacenamos en un diccionario los datos a enviar a ogCore: - data = { - 'job_id': job_id, - 'image_id': image_id - } - # Llamamos al endpoint de ogCore, enviando los datos del diccionario: - journal.send(f"Calling function 'recall_ogcore' (JOB_ID: {job_id}, IMAGE_ID: {image_id})", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - recall_ogcore(data) - break - # Esperamos 10 segundos para volver a realizar la comprobación: - sleep(10) - - -# --------------------------------------------------------- - - -def check_virtual_image_conversion(image_name, job_id): - """ Cada minuto comprueba si existe la imagen convertida (y sus archivos asociados), o si no existe ninguno de los archivos que se crean en el proceso. - Cuando ya no exista el archivo ".lock" (pero si los demás archivos), le comunicará a ogCore que la conversión ha sido exitosa, y saldrá de bucle. - Cuando no exista ninguno de los archivos que se crean en el proceso (incluyendo la imagen convertida), le comunicará a ogCore que la conversión ha fallado, y saldrá de bucle. - Mientras no se cumpla ninguna de las condiciones anteriores, seguirá haciendo la comprobación (repitiendo el bucle cada minuto). - Lo utiliza el endpoint "Convertir imagen virtual a imagen OpenGnsys". - """ - journal.send("Running function 'check_virtual_image_conversion'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - - # Esperamos 30 segundos, para dar tiempo a que se cree algún archivo: - sleep(30) - - # Construimos la ruta de la imagen (una vez convertida): - image_file_path = f"{repo_path}{image_name}.img" - - # Creamos un bucle infinito: - while True: - # Si ya no existe el archivo ".lock" (pero sí existen los demás), respondemos a ogCore (con "success: True") y salimos del bucle: - if not os.path.exists(f"{image_file_path}.lock") and os.path.exists(image_file_path) and os.path.exists(f"{image_file_path}.full.sum") and os.path.exists(f"{image_file_path}.info.checked") and os.path.exists(f"{image_file_path}.size") and os.path.exists(f"{image_file_path}.sum") and os.path.exists(f"{image_file_path}.torrent"): - journal.send("Task finalized (Virtual image converted)", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - journal.send("{'component':'ogRepo', 'severity':'INFO', 'operation':'Run function check_virtual_image_conversion', 'desc':'Virtual image converted'}", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api") - - # Almacenamos en un diccionario los datos a enviar a ogCore: - data = { - 'job_id': job_id, - 'success': True - } - # Llamamos al endpoint de ogCore, enviando los datos del diccionario: - journal.send(f"Calling function 'recall_ogcore' (JOB_ID: {job_id}, SUCCESS: True)", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - recall_ogcore(data) - break - - # Si no existe ninguno de los archivos que se crean en el proceso (incluyendo la imagen convertida), respondemos a ogCore (con "success: False") y salimos del bucle: - elif not os.path.exists(f"{vm_path}{image_name}.raw") and not os.path.exists(f"{vm_path}{image_name}.img") and not os.path.exists(f"{vm_path}{image_name}.img.lzo") and not os.path.exists(f"{repo_path}{image_name}.img"): - journal.send("Virtual image conversion failed", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - journal.send("{'component':'ogRepo', 'severity':'ERROR', 'operation':'Run function check_virtual_image_conversion', 'desc':'Virtual image conversion failed'}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api") - - # Almacenamos en un diccionario los datos a enviar a ogCore: - data = { - 'job_id': job_id, - 'success': False - } - # Llamamos al endpoint de ogCore, enviando los datos del diccionario: - journal.send(f"Calling function 'recall_ogcore' (JOB_ID: {job_id}, SUCCESS: False)", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - recall_ogcore(data) - break - # Si no se han cumplido las condiciones anteriores), imprimimos un mensaje en la API: - else: - journal.send("Task in process (Conversion not finalized)", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - - # Esperamos 1 minuto para volver a realizar la comprobación: - sleep(60) - - -# --------------------------------------------------------- - - -def check_virtual_image_reconversion(image_name, vm_extension, job_id): - """ Cada minuto comprueba si existe la imagen virtual exportada y si ya no existe ninguno de los archivos que se crean en el proceso (o si ya no existen estos archivos ni la imagen exportada). - En el primer caso, le comunicará a ogCore que la conversión ha sido exitosa y saldrá de bucle, y en el segundo caso, le comunicará a ogCore que la conversión ha fallado, y saldrá de bucle. - Mientras no se cumpla ninguna de las condiciones anteriores, seguirá haciendo la comprobación (repitiendo el bucle cada minuto). - Lo utiliza el endpoint "Convertir imagen OpenGnsys a imagen virtual". - """ - journal.send("Running function 'check_virtual_image_reconversion'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - - # Esperamos 30 segundos, para dar tiempo a que se cree algún archivo: - sleep(30) - - # Construimos la ruta de la imagen virtual (una vez exportada), y la ruta de exportación: - virtual_image_file_path = f"{vm_path}export/{image_name}.{vm_extension}" - virtual_export_path = f"{vm_path}export/" - - # Creamos un bucle infinito: - while True: - # Si ya no existen los archivos que se crean durante la conversión, pero si la imagen virtual exportada, respondemos a ogCore (con "success: True") y salimos del bucle: - if not os.path.exists(f"{virtual_export_path}disk.raw") and not os.path.exists(f"{virtual_export_path}{image_name}.img") and os.path.exists(virtual_image_file_path): - journal.send("Task finalized (Image converted to virtual)", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - journal.send("{'component':'ogRepo', 'severity':'INFO', 'operation':'Run function check_virtual_image_reconversion', 'desc':'Image converted to virtual'}", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api") - - # Almacenamos en un diccionario los datos a enviar a ogCore: - data = { - 'job_id': job_id, - 'success': True - } - # Llamamos al endpoint de ogCore, enviando los datos del diccionario: - journal.send(f"Calling function 'recall_ogcore' (JOB_ID: {job_id}, SUCCESS: True)", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - recall_ogcore(data) - break - - # Si no existen los archivos que se crean durante la conversión, pero tampoco la imagen virtual exportada, respondemos a ogCore (con "success: False") y salimos del bucle: - elif not os.path.exists(f"{virtual_export_path}disk.raw") and not os.path.exists(f"{virtual_export_path}{image_name}.img") and not os.path.exists(virtual_image_file_path): - journal.send("Image conversion to virtual failed", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - journal.send("{'component':'ogRepo', 'severity':'ERROR', 'operation':'Run function check_virtual_image_reconversion', 'desc':'Image conversion to virtual failed'}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api") - - # Almacenamos en un diccionario los datos a enviar a ogCore: - data = { - 'job_id': job_id, - 'success': False - } - # Llamamos al endpoint de ogCore, enviando los datos del diccionario: - journal.send(f"Calling function 'recall_ogcore' (JOB_ID: {job_id}, SUCCESS: False)", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - recall_ogcore(data) - break - # Si no se han cumplido las condiciones anteriores), imprimimos un mensaje en la API: - else: - journal.send("Task in process (Conversion not finalized)", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - - # Esperamos 1 minuto para volver a realizar la comprobación: - sleep(60) - - -# --------------------------------------------------------- - - -def recall_ogcore(data): - """ Hace una petición HTTP de tipo POST a un endpoint de ogCore, enviando datos que dependen del caso. - Se utiliza para informar a ogCore del resultado de una tarea asíncrona, - que estaba corriendo en un proceso independiente (no controlado por los endpoints). - """ - journal.send("Running function 'recall_ogcore'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - - # Almacenamos la IP de ogCore: - ogcore_ip = get_IPcore() - - # Almacenamos la URL del endpoint de ogCore: - endpoint_url = f"https://{ogcore_ip}:8443/og-repository/webhook" - - # Almacenamos los headers, y convertiomos "data" a JSON: - headers = {'content-type': 'application/json'} - data = json.dumps(data) - - # Hacemos una petición POST al endpoint, enviando lo almacenado en "data": - journal.send("Sending HTTP request...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - response = requests.post(endpoint_url, data=data, headers=headers, verify=False) - - # Imprimimos el código de estado de la petición y la respuesta de ogCore: - journal.send(f"HTTP Status Code: {response.status_code}", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - #journal.send(f"HTTP Response: {response.text}", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - journal.send(f"{{'component':'ogRepo', 'severity':'INFO', 'operation':'Run function recall_ogcore', 'desc':'HTTP Status Code response: {response.status_code}'}}", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api") - - -# --------------------------------------------------------- - - -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". - """ - journal.send("Running function 'check_file_exists'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - - # Comprobamos si existe el archivo de la ruta "file_path": - if os.path.exists(file_path): - return True - else: - return False - - -# --------------------------------------------------------- - - -def remove_remote_files(sftp_client, remote_path, image_name, extensions): - """ Borra la imagen "image_name" y sus archivos asociados (cuyas extensiones están almacenadas en la lista "extensions") - del equipo remoto al que nos hemos conectado previamente (mediante el objeto "sftp_client", que recibe como primer parámetro). - Es llamada por la función "check_remote_backup", cuando la imagen exportada no pasa el test de integridad. - """ - journal.send("Running function 'remove_remote_files'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - - # Iteramos las extensiones de la lista "extensions", para eliminar la imagen "image_name" y sus archivos asociados: - for ext in extensions: - try: - sftp_client.stat(f"{remote_path}{image_name}{ext}") - sftp_client.remove(f"{remote_path}{image_name}{ext}") - journal.send(f"File with extension {ext} removed", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - except IOError: - journal.send(f"File with extension {ext} doesn't exist", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - - -# --------------------------------------------------------- - - -def check_free_space(image_name_full, path): - """ Comprueba si hay suficiente espacio en disco para convertir la imagen que recibe como parámetro - (4 veces el tamaño del archivo), devolviendo "True" si lo hay, y "False" si no lo hay. - Lo utilizan los endpoints "Convertir imagen virtual a imagen OpenGnsys" y "Convertir imagen OpenGnsys a imagen virtual". - """ - # Obtenemos el tamaño de la imagen: - img_size = int(os.path.getsize(f"{path}{image_name_full}")) - # Obtenemos la cantidad de espacio libre en disco: - disk = psutil.disk_usage('/') - free_space = int(disk.free) - - # Si no hay suficiente espacio libre en disco (4 veces el tamaño de la imagen), devolvemos "False", y en caso contrario "True": - if free_space < (img_size * 4): - return False - else: - return True - - - -# -------------------------------------------------------------------------------------------- -# ENDPOINTS -# -------------------------------------------------------------------------------------------- - - -# 1 - Endpoint "Obtener Información de Estado de ogRepository" (SINCRONO): -@app.route("/ogrepository/v1/status", methods=['GET']) -def get_repo_status(): - """ Este endpoint devuelve información de CPU, memoria RAM, disco duro y el estado de ciertos servicios y procesos de ogRepository, en formato json. - Para ello, ejecuta el script "getRepoStatus.py", que no recibe parámetros. - """ - journal.send("Running endpoint 'Obtener Información de Estado de ogRepository'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - - try: - 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") - journal.send("{'component':'ogRepo', 'severity':'INFO', 'http_code':'200', 'operation':'Run script getRepoStatus.py', 'desc':'Result OK (ReturnCode: 0)'}", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api") - return jsonify({ - "success": True, - "output": json.loads(result.stdout) - }), 200 - else: - journal.send(f"Script 'getRepoStatus.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 getRepoStatus.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 'getRepoStatus.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 getRepoStatus.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 - - -# --------------------------------------------------------- - - -# 2 - Endpoint "Obtener Información de todas las Imágenes" (SINCRONO): -@app.route("/ogrepository/v1/images", methods=['GET']) -def get_repo_info(): - """ Este endpoint devuelve información de todas las imágenes contenidas en el repositorio (incluída la papelera), en formato json. - Para ello, ejecuta el script "getRepoInfo.py", con el parámetro "all". - """ - journal.send("Running endpoint 'Obtener Información de todas las Imágenes'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - - try: - journal.send("Running script 'getRepoInfo.py'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - # Ejecutamos el script "getRepoInfo.py" (con el parámetro "all"), y almacenamos el resultado: - result = subprocess.run(['python3', f"{script_path}/getRepoInfo.py", 'all'], 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 'getRepoInfo.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 getRepoInfo.py', 'desc':'Result OK (ReturnCode: 0)'}", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api") - return jsonify({ - "success": True, - "output": json.loads(result.stdout) - }), 200 - else: - journal.send(f"Script 'getRepoInfo.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 getRepoInfo.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 'getRepoInfo.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 getRepoInfo.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 - - -# --------------------------------------------------------- - - -# 3 - Endpoint "Obtener Información de una Imagen concreta" (SINCRONO): -@app.route("/ogrepository/v1/images/", methods=['GET']) -def get_repo_image_info(imageId): - """ Este endpoint devuelve información de la imagen especificada como parámetro, en formato json. - Para ello, ejecuta el script "getRepoInfo.py", con el nombre de la imagen como parámetro. - """ - journal.send("Running endpoint 'Obtener Información de una Imagen concreta'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - - # Obtenemos el nombre y la extensión de la imagen: - param_dict = get_image_params(imageId) - - # 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: - cmd = ['python3', f"{script_path}/getRepoInfo.py", f"{param_dict['name']}.{param_dict['extension']}"] - 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 get_repo_image_info', 'desc': 'Warning: Image not found'}", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api") - return jsonify({ - "success": False, - "error": "Image not found" - }), 400 - - try: - journal.send("Running script 'getRepoInfo.py'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - # Ejecutamos el script "getRepoInfo.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 'getRepoInfo.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 getRepoInfo.py', 'desc':'Result OK (ReturnCode: 0)'}", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api") - return jsonify({ - "success": True, - "output": json.loads(result.stdout) - }), 200 - else: - journal.send(f"Script 'getRepoInfo.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 getRepoInfo.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 'getRepoInfo.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 getRepoInfo.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 - - -# --------------------------------------------------------- - - -# 4 - Endpoint "Actualizar Información del Repositorio" (SINCRONO): -@app.route("/ogrepository/v1/images", methods=['PUT']) -def update_repo_info(): - """ Este endpoint actualiza la información del repositorio y de la papelera, reflejándola en los archivos "repoinfo.json" y "trashinfo.josn". - Para ello, ejecuta el script "updateRepoInfo.py", que a su vez ejecuta el script "updateTrashInfo.py". - """ - journal.send("Running endpoint 'Actualizar Información del Repositorio'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - - try: - journal.send("Running script 'updateRepoInfo.py'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - # Ejecutamos el script "updateRepoInfo.py" (sin parámetros), y almacenamos el resultado: - result = subprocess.run(['python3', f"{script_path}/updateRepoInfo.py"], check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding='UTF8') - - # Evaluamos el resultado de la ejecución, y devolvemos una respuesta: - if result.returncode == 0: - journal.send("Script 'updateRepoInfo.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 updateRepoInfo.py', 'desc':'Result OK (ReturnCode: 0)'}", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api") - return jsonify({ - "success": True, - "output": "Repository info updated successfully" - }), 200 - else: - journal.send(f"Script 'updateRepoInfo.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 updateRepoInfo.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 'updateRepoInfo.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 updateRepoInfo.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 - - -# --------------------------------------------------------- - - -# 5 - Endpoint "Chequear Integridad de Imagen" (SINCRONO): -@app.route("/ogrepository/v1/status/images/", methods=['GET']) -def check_image(imageId): - """ Este endpoint comprueba la integridad de la imagen especificada como parámetro, - comparando el tamaño y el hash MD5 del último MB con los valores almacenados en los archivos "size" y "sum". - Para ello, ejecuta el script "checkImage.py", con el nombre de la imagen como único parámetro. - """ - journal.send("Running endpoint 'Chequear Integridad de Imagen'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - - # Obtenemos el nombre y la extensión de la imagen: - param_dict = get_image_params(imageId, "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: - cmd = ['python3', f"{script_path}/checkImage.py", f"{param_dict['name']}.{param_dict['extension']}"] - else: - journal.send("Image not found (inexistent or deleted)", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - journal.send("{'component':'ogRepo', 'severity':'WARNING', 'http_code':'400', 'operation':'Run endpoint check_image', 'desc':'Warning: Image not found'}", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api") - return jsonify({ - "success": False, - "error": "Image not found (inexistent or deleted)" - }), 400 - - try: - journal.send("Running script 'checkImage.py'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - # Ejecutamos el script "checkImage.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 'checkImage.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 checkImage.py', 'desc':'Result OK (ReturnCode: 0)'}", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api") - if "Error" in result.stdout: - return jsonify({ - "success": True, - "output": "Image file didn't pass the Integrity Check" - }), 200 - else: - return jsonify({ - "success": True, - "output": "Image file passed the Integrity Check correctly" - }), 200 - else: - journal.send(f"Script 'checkImage.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 checkImage.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 'checkImage.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 checkImage.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 - - -# --------------------------------------------------------- - - -# 6 - Endpoint "Eliminar una Imagen" (SINCRONO): -@app.route("/ogrepository/v1/images/", methods=['DELETE']) -def delete_image(imageId): - """ Este endpoint elimina la imagen especificada como parámetro (y todos sus archivos asociados), - moviéndolos a la papelera o eliminándolos permanentemente (dependiendo del parámetro "method"). - Para ello, ejecuta el script "deleteImage.py", con el nombre de la imagen como primer parámetro, y el parámetro opcional "-p". - """ - journal.send("Running endpoint 'Eliminar una Imagen'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - - # Obtenemos el nombre y la extensión de la imagen: - param_dict = get_image_params(imageId, "repo") - # Almacenams el método de eliminación ("trash" o "permanent") - method = request.values.get('method') - - # 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: - if method == "permanent": - cmd = ['python3', f"{script_path}/deleteImage.py", f"{param_dict['name']}.{param_dict['extension']}", '-p'] - elif method == "trash": - cmd = ['python3', f"{script_path}/deleteImage.py", f"{param_dict['name']}.{param_dict['extension']}"] - else: - journal.send("Incorrect method (must be 'permanent' or 'trash')", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - journal.send("{'component':'ogRepo', 'severity':'WARNING', 'http_code':'400', 'operation':'Run endpoint delete_image', 'desc':'Warning: Incorrect method (must be permanent or trash)'}", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api") - return jsonify({ - "success": False, - "error": "Incorrect method (must be 'permanent' or 'trash')" - }), 400 - else: - journal.send("Image not found (inexistent or deleted)", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - journal.send("{'component':'ogRepo', 'severity':'WARNING', 'http_code':'400', 'operation':'Run endpoint delete_image', 'desc':'Warning: Image not found (inexistent or deleted)'}", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api") - return jsonify({ - "success": False, - "error": "Image not found (inexistent or deleted)" - }), 400 - - try: - journal.send("Running script 'deleteImage.py'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - # Ejecutamos el script "deleteImage.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 una respuesta: - if result.returncode == 0: - journal.send("Script 'deleteImage.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 deleteImage.py', 'desc':'Result OK (ReturnCode: 0)'}", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api") - return jsonify({ - "success": True, - "output": "Image deleted successfully" - }), 200 - else: - journal.send(f"Script 'deleteImage.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 deleteImage.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 'deleteImage.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 deleteImage.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 - - -# --------------------------------------------------------- - - -# 7 - Endpoint "Recuperar una Imagen" (SINCRONO): -@app.route("/ogrepository/v1/trash/images", methods=['POST']) -def recover_image(): - """ Este endpoint recupera la imagen especificada como parámetro (y todos sus archivos asociados), - moviéndolos nuevamente al repositorio de imágenes, desde la papelera. - Para ello, ejecuta el script "recoverImage.py", con el nombre de la imagen como único parámetro. - """ - journal.send("Running endpoint 'Recuperar una Imagen'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - - # Almacenamos el parámetro "ID_img" (enviado por JSON): - json_data = json.loads(request.data) - 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") - - # 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: - cmd = ['python3', f"{script_path}/recoverImage.py", f"{param_dict['name']}.{param_dict['extension']}"] - else: - journal.send("Image not found (inexistent or recovered previously)", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - journal.send("{'component':'ogRepo', 'severity':'WARNING', 'http_code':'400', 'operation':'Run endpoint recover_image', 'desc':'Warning: Image not found (inexistent or recovered previously)'}", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api") - return jsonify({ - "success": False, - "error": "Image not found (inexistent or recovered previously)" - }), 400 - - try: - journal.send("Running script 'recoverImage.py'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - # Ejecutamos el script "recoverImage.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 una respuesta: - if result.returncode == 0: - journal.send("Script 'recoverImage.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 recoverImage.py', 'desc':'Result OK (ReturnCode: 0)'}", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api") - return jsonify({ - "success": True, - "output": "Image recovered successfully" - }), 200 - else: - journal.send(f"Script 'recoverImage.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 recoverImage.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 'recoverImage.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 recoverImage.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 - - -# --------------------------------------------------------- - - -# 8 - Endpoint "Eliminar una Imagen de la Papelera" (SINCRONO): -@app.route("/ogrepository/v1/trash/images/", methods=['DELETE']) -def delete_trash_image(imageId): - """ Este endpoint elimina permanentemente la imagen especificada como parámetro (y todos sus archivos asociados), desde la papelera. - Para ello, ejecuta el script "deleteTrashImage.py", con el nombre de la imagen como único parámetro. - """ - journal.send("Running endpoint 'Eliminar una Imagen de la Papelera'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - - # Obtenemos el nombre y la extensión de la imagen: - param_dict = get_image_params(imageId, "trash") - - # 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: - cmd = ['python3', f"{script_path}/deleteTrashImage.py", f"{param_dict['name']}.{param_dict['extension']}"] - else: - journal.send("Image not found at trash", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - journal.send("{'component':'ogRepo', 'severity':'WARNING', 'http_code':'400', 'operation':'Run endpoint delete_trash_image', 'desc':'Warning: Image not found at trash'}", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api") - return jsonify({ - "success": False, - "error": "Image not found at trash" - }), 400 - - try: - journal.send("Running script 'deleteTrashImage.py'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - # Ejecutamos el script "deleteTrashImage.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 una respuesta: - if result.returncode == 0: - journal.send("Script 'deleteTrashImage.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 deleteTrashImage.py', 'desc':'Result OK (ReturnCode: 0)'}", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api") - return jsonify({ - "success": True, - "output": "Image deleted successfully" - }), 200 - else: - journal.send(f"Script 'deleteTrashImage.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 deleteTrashImage.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 'deleteTrashImage.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 deleteTrashImage.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 - - -# --------------------------------------------------------- - - -# 9 - Endpoint "Importar una Imagen" (ASINCRONO): -@app.route("/ogrepository/v1/repo/images", methods=['POST']) -def import_image(): - """ Este endpoint importa la imagen especificada como primer parámetro (y todos sus archivos asociados), desde un servidor remoto al servidor local. - Para ello, ejecuta el script "importImage.py", con el nombre de la imagen como primer parámetro, - la IP o hostname del servidor remoto como segundo parámetro, y el usuario con el que conectar al servidor como tercer parámetro. - """ - journal.send("Running endpoint 'Importar 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_name = json_data.get("image") - remote_ip = json_data.get("repo_ip") - remote_user = json_data.get("user") - - # Comprobamos la conexión con el equipo remoto, y si falla salimos del endpoint, retornando un error: - connection_OK = check_remote_connection(remote_ip, remote_user) - if connection_OK == False: - journal.send("Can't connect to remote server", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - journal.send("{'component':'ogRepo', 'severity':'ERROR', 'http_code':'400', 'operation':'Run endpoint import_image', 'desc':'Unable to connect to remote server'}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api") - return jsonify({ - "success": False, - "exception": "Can't connect to remote server" - }), 400 - - # Construimos la ruta de la imagen: - image_file_path = f"{repo_path}{image_name}" - - # Comprobamos si la imagen remota no existe o está bloqueada, en cuyos casos salimos del endpoint y retornamos el error correspondiente: - check_image = check_remote_image(remote_ip, remote_user, image_file_path) - if check_image == "Remote image not found": - journal.send("Remote image not found", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - journal.send("{'component':'ogRepo', 'severity':'WARNING', 'http_code':'400', 'operation':'Run endpoint import_image', 'desc':'Warning: Remote image not found'}", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api") - return jsonify({ - "success": False, - "exception": "Remote image not found" - }), 400 - elif check_image == "Remote image is locked": - journal.send("Remote image is locked", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - journal.send("{'component':'ogRepo', 'severity':'WARNING', 'http_code':'400', 'operation':'Run endpoint import_image', 'desc':'Warning: Remote image is locked'}", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api") - return jsonify({ - "success": False, - "exception": "Remote image is locked" - }), 400 - - # Construimos la llamada al script: - cmd = ['python3', f"{script_path}/importImage.py", image_file_path, remote_ip, remote_user] - - try: - journal.send("Running script 'importImage.py'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - # Ejecutamos el script "importImage.py" (con los parámetros almacenados), y almacenamos el resultado (pero sin esperar a que termine el proceso): - result = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding='UTF8') - - # Generamos el ID para identificar el trabajo asíncrono: - job_id = f"TransferImage_{''.join(random.choice('0123456789abcdef') for char in range(8))}" - journal.send(f"JOB ID generated ({job_id})", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - - # Evaluamos el resultado de la ejecución, y devolvemos una respuesta: - 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") - threading.Thread(target=check_lock_local, args=(image_file_path, job_id,)).start() - - # Informamos que la imagen se está importando, y salimos del endpoint: - return jsonify({ - "success": True, - "output": "Importing image...", - "job_id": job_id - }), 200 - else: - journal.send("Script 'importImage.py' result KO (Image import failed)", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - journal.send("{'component':'ogRepo', 'severity':'ERROR', 'http_code':'500', 'operation':'Run script importImage.py', 'desc':'Result KO (Error: Image import failed)'}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api") - return jsonify({ - "success": False, - "error": "Image import failed" - }), 500 - except subprocess.CalledProcessError as error: - journal.send(f"Script 'importImage.py' result KO (Process Exception: {str(error)})", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - journal.send(f"{{'component':'ogRepo', 'severity':'ERROR', 'http_code':'500', 'operation':'Run script importImage.py', 'desc':'Result KO (Process Exception: {str(error)})'}}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api") - return jsonify({ - "success": False, - "process exception": str(error) - }), 500 - except Exception as error_description: - journal.send(f"Script 'importImage.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 importImage.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 - - -# --------------------------------------------------------- - - -# 10 - Endpoint "Hacer backup de una Imagen" (ASINCRONO): -@app.route("/ogrepository/v1/repo/images", methods=['PUT']) -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. - """ - journal.send("Running endpoint 'Hacer backup de 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") - remote_ip = json_data.get("repo_ip") - remote_user = json_data.get("user") - remote_path = json_data.get("remote_path") - - # Si el úitimo carácter de "remote_path" no es una barra, la añadimos: - if remote_path[-1] != "/": - remote_path = f"{remote_path}/" - - # 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 ruta de la imagen, o para devolver un error si no se ha encontrado la imagen (o si está bloqueada): - if param_dict: - image_name = f"{param_dict['name']}.{param_dict['extension']}" - - # Comprobamos si el archivo que bloquea la imagen existe, llamando a la función "check_file_exists": - image_lock_exists = check_file_exists(f"{repo_path}{image_name}.lock") - - # Si la imagen existe pero está bloqueada, devolvemos un error: - if image_lock_exists == True: - journal.send("Image is locked", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - journal.send("{'component':'ogRepo', 'severity':'WARNING', 'http_code':'400', 'operation':'Run endpoint backup_image', 'desc':'Warning: Image is locked'}", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api") - return jsonify({ - "success": False, - "exception": "Image is locked" - }), 400 - 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 backup_image', 'desc':'Warning: Image not found'}", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api") - return jsonify({ - "success": False, - "error": "Image not found" - }), 400 - - # Comprobamos la conexión con el equipo remoto, y si falla salimos del endpoint, retornando un error: - connection_OK = check_remote_connection(remote_ip, remote_user) - - if connection_OK == False: - journal.send("Can't connect to remote server", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - journal.send("{'component':'ogRepo', 'severity':'ERROR', 'http_code':'400', 'operation':'Run endpoint backup_image', 'desc':'Unable to connect to remote host'}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api") - return jsonify({ - "success": False, - "exception": "Can't connect to remote host" - }), 400 - - # Construimos la llamada al script: - cmd = ['python3', f"{script_path}/backupImage.py", image_name, remote_ip, remote_user, remote_path] - - try: - journal.send("Running script 'backupImage.py'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - # Ejecutamos el script "backupImage.py" (con los parámetros almacenados), y almacenamos el resultado (pero sin esperar a que termine el proceso): - result = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding='UTF8') - - # Generamos el ID para identificar el trabajo asíncrono: - job_id = f"BackupImage_{''.join(random.choice('0123456789abcdef') for char in range(8))}" - journal.send(f"JOB ID generated ({job_id})", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - - # Evaluamos el resultado de la ejecución, y devolvemos una respuesta: - if result.returncode is None: - journal.send("Script 'backupImage.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 backupImage.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_remote_backup" en un hilo paralelo - # (para que compruebe si la imagen se ha acabado de copiar exitosamente): - journal.send("Calling function 'check_remote_backup'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - threading.Thread(target=check_remote_backup, args=(image_name, remote_ip, remote_user, remote_path, job_id,)).start() - - # Informamos que la imagen se está exportando, y salimos del endpoint: - return jsonify({ - "success": True, - "output": "Making image backup...", - "job_id": job_id - }), 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") - return jsonify({ - "success": False, - "error": "Backup image failed" - }), 500 - except subprocess.CalledProcessError as error: - journal.send(f"Script 'backupImage.py' result KO (Process Exception: {str(error)})", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - journal.send(f"{{'component':'ogRepo', 'severity':'ERROR', 'http_code':'500', 'operation':'Run script backupImage.py', 'desc':'Result KO (Process Exception: {str(error_)})'}}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api") - return jsonify({ - "success": False, - "process exception": str(error) - }), 500 - except Exception as error_description: - journal.send(f"Script 'backupImage.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 backupImage.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 - - -# --------------------------------------------------------- - - -# 11 - Endpoint "Crear archivos auxiliares" (ASINCRONO): -@app.route("/ogrepository/v1/images/torrentsum", methods=['POST']) -def create_torrent_sum(): - """ Este endpoint crea los archivos ".size", ".sum", ".full.sum" y ".torrent" para la imagen que recibe como parámetro. - Para ello, ejecuta el script "createTorrentSum.py", con el nombre de la imagen como único parámetro. - """ - journal.send("Running endpoint 'Crear archivos auxiliares'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - - # Almacenamos los parámetros enviados en el JSON: - json_data = json.loads(request.data) - image_name = json_data.get("image") - - # Comprobamos si la imagen existe, llamando a la función "check_file_exists": - image_exists = check_file_exists(f"{repo_path}{image_name}") - - # Si la imagen no existe, retornamos un error y salimos del endpoint: - if image_exists == False: - 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 create_torrent_sum', 'desc':'Warning: Image not found'}", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api") - return jsonify({ - "success": False, - "exception": "Image not found" - }), 400 - - # Construimos la ruta de la imagen (relativa a "repo_path"): - image_file_path = image_name - - # Construimos la llamada al script: - cmd = ['python3', f"{script_path}/createTorrentSum.py", image_file_path] - - try: - journal.send("Running script 'createTorrentSum.py'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - # Ejecutamos el script "createTorrentSum.py" (con los parámetros almacenados), y almacenamos el resultado (pero sin esperar a que termine el proceso): - result = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding='UTF8') - - # Generamos el ID para identificar el trabajo asíncrono: - job_id = f"CreateAuxiliarFiles_{''.join(random.choice('0123456789abcdef') for char in range(8))}" - journal.send(f"JOB ID generated ({job_id})", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - - # Evaluamos el resultado de la ejecución, y devolvemos una respuesta: - if result.returncode is None: - journal.send("Script 'createTorrentSum.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 createTorrentSum.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_aux_files" en un hilo paralelo - # (para que compruebe si se han creado todos los archivos auxiliares exitosamente): - journal.send("Calling function 'check_aux_files'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - threading.Thread(target=check_aux_files, args=(f"{repo_path}{image_file_path}", job_id,)).start() - - # Informamos que los archivos auxiliares se están creando, y salimos del endpoint: - return jsonify({ - "success": True, - "output": "Creating auxiliar files...", - "job_id": job_id - }), 200 - else: - journal.send(f"Script 'createTorrentSum.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 createTorrentSum.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 'createTorrentSum.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 createTorrentSum.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 - - -# --------------------------------------------------------- - - -# 12 - Endpoint "Enviar paquete Wake On Lan" (SINCRONO): -@app.route("/ogrepository/v1/wol", methods=['POST']) -def send_wakeonlan(): - """ Este endpoint envía un paquete mágico Wake On Lan a la dirección MAC especificada, a través de la IP de broadcast especificadac. - Para ello, ejecuta el script "sendWakeOnLan.py", con la IP de broadcast como primer parámetro, y la MAC como segundo parámetro. - """ - journal.send("Running endpoint 'Enviar paquete Wake On Lan'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - - # Almacenamos los parámetros enviados en el JSON: - json_data = json.loads(request.data) - broadcast_ip = json_data.get("broadcast_ip") - mac = json_data.get("mac") - - try: - journal.send("Running script 'sendWakeOnLan.py'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - # Ejecutamos el script "sendWakeOnLan.py" (con los parámetros almacenados), y almacenamos el resultado: - result = subprocess.run(['python3', f"{script_path}/sendWakeOnLan.py", broadcast_ip, mac], check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding='UTF8') - - # Evaluamos el resultado de la ejecución, y devolvemos una respuesta: - if result.returncode == 0: - journal.send("Script 'sendWakeOnLan.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 sendWakeOnLan.py', 'desc':'Result OK (ReturnCode: 0)'}", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api") - return jsonify({ - "success": True, - "output": "Wake On Lan packet sended successfully" - }), 200 - else: - journal.send(f"Script 'sendWakeOnLan.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 sendWakeOnLan.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 'sendWakeOnLan.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 sendWakeOnLan.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 - - -# --------------------------------------------------------- - - -# 13 - Endpoint "Enviar una Imagen mediante UDPcast" (ASINCRONO): -@app.route("/ogrepository/v1/udpcast", methods=['POST']) -def send_udpcast(): - """ Este endpoint envía mediante UDPcast la imagen que recibe como primer parámetro, con los datos de transferencia que recibe en los demás parámetros. - Para ello, ejecuta el script "sendFileMcast.py", con la imagen como primer parámetro, y los demás en una cadena (como segundo parámetro). - """ - journal.send("Running endpoint 'Enviar una Imagen mediante UDPcast'...", 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") - port = json_data.get("port") - method = json_data.get("method") - ip = json_data.get("ip") - bitrate = json_data.get("bitrate") - nclients = json_data.get("nclients") - maxtime = json_data.get("maxtime") - - # 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: - cmd = ['python3', f"{script_path}/sendFileMcast.py", f"{param_dict['name']}.{param_dict['extension']}", f"{port}:{method}:{ip}:{bitrate}:{nclients}:{maxtime}"] - 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 send_udpcast', 'desc':'Warning: Image not found'}", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api") - return jsonify({ - "success": False, - "error": "Image not found" - }), 400 - - try: - journal.send("Running script 'sendFileMcast.py'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - # Ejecutamos el script "sendFileMcast.py" (con los parámetros almacenados), y almacenamos el resultado (pero sin esperar a que termine el proceso): - result = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding='UTF8') - - # Comprobamos si está corriendo el proceso correspondiente de "udp-sender" (esperando 5 segundos para darle tiempo a iniciarse): - sleep(5) - process_running = search_process('udp-sender', f"{param_dict['name']}.{param_dict['extension']}") - - # Evaluamos el resultado de la ejecución, y devolvemos una respuesta: - if result.returncode is None and process_running == True: - journal.send("Script 'sendFileMcast.py' result OK (ReturnCode: None), and process running", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - journal.send("{'component':'ogRepo', 'severity':'INFO', 'http_code':'200', 'operation':'Run script sendFileMcast.py', 'desc':'Result OK (ReturnCode: None), and process running'}", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api") - return jsonify({ - "success": True, - "output": "Sending image..." - }), 200 - else: - journal.send("Script 'sendFileMcast.py' result KO (Image send failed)", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - journal.send("{'component':'ogRepo', 'severity':'ERROR', 'http_code':'500', 'operation':'Run script sendFileMcast.py', 'desc':'Result KO (Image send failed)'}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api") - return jsonify({ - "success": False, - "error": "Image send failed" - }), 500 - except subprocess.CalledProcessError as error: - journal.send(f"Script 'sendFileMcast.py' result KO (Process Exception: {str(error)})", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - journal.send(f"{{'component':'ogRepo', 'severity':'ERROR', 'http_code':'500', 'operation':'Run script sendFileMcast.py', 'desc':'Result KO (Process Exception: {str(error)})'}}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api") - return jsonify({ - "success": False, - "process exeption": str(error) - }), 500 - except Exception as error_description: - journal.send(f"Script 'sendFileMcast.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 sendFileMcast.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 - - -# --------------------------------------------------------- - - -# 14 - Endpoint "Enviar una Imagen mediante UFTP" (ASINCRONO): -@app.route("/ogrepository/v1/uftp", methods=['POST']) -def send_uftp(): - """ Este endpoint envía mediante UFTP la imagen que recibe como primer parámetro, con los datos de transferencia que recibe en los demás parámetros. - Para ello, ejecuta el script "sendFileUFTP.py", con la imagen como primer parámetro, y los demás en una cadena (como segundo parámetro). - NOTA: Es necesario que los clientes se hayan puesto en escucha previamente (ejecutando el script "listenUFTPD.py"). - """ - journal.send("Running endpoint 'Enviar una Imagen mediante UFTP'...", 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") - port = json_data.get("port") - ip = json_data.get("ip") - bitrate = json_data.get("bitrate") - - # 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: - cmd = ['python3', f"{script_path}/sendFileUFTP.py", f"{param_dict['name']}.{param_dict['extension']}", f"{port}:{ip}:{bitrate}"] - 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 send_uftp', 'desc':'Warning: Image not found'}", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api") - return jsonify({ - "success": False, - "error": "Image not found" - }), 400 - - try: - journal.send("Running script 'sendFileUFTP.py'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - # Ejecutamos el script "sendFileUFTP.py" (con los parámetros almacenados), y almacenamos el resultado (pero sin esperar a que termine el proceso): - result = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding='UTF8') - - # Comprobamos si está corriendo el proceso correspondiente de "uftp" (esperando 5 segundos para darle tiempo a iniciarse): - sleep(5) - process_running = search_process('uftp', f"{param_dict['name']}.{param_dict['extension']}") - - # Evaluamos el resultado de la ejecución, y devolvemos una respuesta: - if result.returncode is None and process_running == True: - journal.send("Script 'sendFileUFTP.py' result OK (ReturnCode: None), and process running", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - journal.send("{'component':'ogRepo', 'severity':'INFO', 'http_code':'200', 'operation':'Run script sendFileUFTP.py', 'desc':'Result OK (ReturnCode: None), and process running'}", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api") - return jsonify({ - "success": True, - "output": "Sending image..." - }), 200 - else: - journal.send("Script 'sendFileUFTP.py' result KO (Image send failed)", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - journal.send("{'component':'ogRepo', 'severity':'ERROR', 'http_code':'500', 'operation':'Run script sendFileUFTP.py', 'desc':'Result KO (Image send failed)'}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api") - return jsonify({ - "success": False, - "error": "Image send failed" - }), 500 - except subprocess.CalledProcessError as error: - journal.send(f"Script 'sendFileUFTP.py' result KO (Process Exception: {str(error)})", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - journal.send(f"{{'component':'ogRepo', 'severity':'ERROR', 'http_code':'500', 'operation':'Run script sendFileUFTP.py', 'desc':'Result KO (Process Exception: {str(error)})'}}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api") - return jsonify({ - "success": False, - "process exeption": str(error) - }), 500 - except Exception as error_description: - journal.send(f"Script 'sendFileUFTP.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 sendFileUFTP.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 - - -# --------------------------------------------------------- - - -# 15 - Endpoint "Enviar una Imagen mediante P2P" (ASINCRONO): -@app.route("/ogrepository/v1/p2p", methods=['POST']) -def send_p2p(): - """ Este endpoint inicia el tracker "bttrack" y el seeder "bittornado", en el directorio de imágenes (sirviendo todas las imágenes). - Para ello, ejecuta los scripts "runTorrentTracker.py" y "runTorrentSeeder.py", que no reciben parámetros. - """ - journal.send("Running endpoint 'Enviar una Imagen mediante P2P'...", 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") - - # 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 las llamadas a los scripts, o para devolver un error si no se ha encontrado la imagen: - if param_dict: - cmd_tracker = ['sudo', 'python3', f"{script_path}/runTorrentTracker.py"] # Este script si que requiere ser ejecutado con "sudo" - cmd_seeder = ['sudo', 'python3', f"{script_path}/runTorrentSeeder.py"] # Este script si que requiere ser ejecutado con "sudo" - base_path = repo_path.rstrip('/') # Le quito la última barra para poder buscar correctamente en los procesos - 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 send_p2p', 'desc':'Warning: Image not found'}", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api") - return jsonify({ - "success": False, - "error": "Image not found" - }), 400 - - # Ejecutamos los scripts "runTorrentSeeder.py" y "runTorrentSeeder.py", que no reciben parámetros. - # NOTA: No almacenamos la salida ni comprobamos los errores, porque los procesos quedarán corriendo hasta que se finalicen manualmente, - # por lo que no podemos comprobar el returncode (luego comprobaremos si los procesos se han iniciado correctamente). - journal.send("Running script 'runTorrentTracker.py'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - subprocess.Popen(cmd_tracker) - - journal.send("Running script 'runTorrentSeeder.py'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - subprocess.Popen(cmd_seeder) - - # Comprobamos si el tracker y el seeder están corriendo, y si apuntan al directorio que le hemos pasado - # (esperamos 10 segundos antes de hacerlo, porque los procesos no se inician inmediatamente): - sleep(10) - tracker_running = search_process('bttrack', base_path) - seeder_running = search_process('btlaunchmany', base_path) - - # Evaluamos las comprobaciones anteriores, para devolver la respuesta que corresponda: - if tracker_running and seeder_running: - journal.send("Scripts 'runTorrentTracker.py' and 'runTorrentSeeder.py' results OK (ReturnCodes: None), and processes running", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - journal.send("{'component':'ogRepo', 'severity':'INFO', 'http_code':'200', 'operation':'Run scripts runTorrentTracker.py and runTorrentSeeder.py', 'desc':'Results OK (ReturnCodes: None), and processes running'}", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api") - return jsonify({ - "success": True, - "output": "Tracker and Seeder serving image correctly" - }), 200 - else: - journal.send("Scripts 'runTorrentTracker.py' and 'runTorrentSeeder.py' results KO (Tracker or/and Seeder not runnig)", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - journal.send("{'component':'ogRepo', 'severity':'ERROR', 'http_code':'500', 'operation':'Run scripts runTorrentTracker.py and runTorrentSeeder.py', 'desc':'Results KO (Tracker or/and Seeder not runnig)'}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api") - return jsonify({ - "success": False, - "error": "Tracker or Seeder (or both) not running" - }), 500 - - -# --------------------------------------------------------- - - -# 16 - Endpoint "Ver Estado de Transmisiones UDPcast" (SINCRONO): -@app.route("/ogrepository/v1/udpcast", methods=['GET']) -def get_udpcast_info(): - """ Este endpoint devuelve información sobre los procesos de "udp-sender" activos, en formato json, - lo que en la práctica permite comprobar las transferencias UDPcast activas. - Para ello, ejecuta el script "getUDPcastInfo.py", que no recibe parámetros. - """ - journal.send("Running endpoint 'Ver Estado de Transmisiones UDPcast'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - - try: - journal.send("Running script 'getUDPcastInfo.py'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - # Ejecutamos el script "getUDPcastInfo.py", y almacenamos el resultado: - result = subprocess.run(['python3', f"{script_path}/getUDPcastInfo.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 'getUDPcastInfo.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 getUDPcastInfo.py', 'desc':'Result OK (ReturnCode: 0)'}", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api") - return jsonify({ - "success": True, - "output": json.loads(result.stdout) - }), 200 - else: - journal.send(f"Script 'getUDPcastInfo.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 getUDPcastInfo.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: - if "exit status 1" in str(error_description): - journal.send("No UDPcast active transmissions", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - journal.send("{'component':'ogRepo', 'severity':'WARNING', 'http_code':'400', 'operation':'Run script getUDPcastInfo.py', 'desc':'Warning: No UDPcast active transmissions'}", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api") - return jsonify({ - "success": False, - "exception": "No UDPCast active transmissions" - }), 400 - else: - journal.send(f"Script 'getUDPcastInfo.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 getUDPcastInfo.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 - - -# --------------------------------------------------------- - - -# 17 - Endpoint "Ver Estado de Transmisiones UFTP" (SINCRONO): -@app.route("/ogrepository/v1/uftp", methods=['GET']) -def get_uftp_info(): - """ Este endpoint devuelve información sobre los procesos de "uftp" activos, en formato json, - lo que en la práctica permite comprobar las transferencias UFTP activas. - Para ello, ejecuta el script "getUFTPInfo.py", que no recibe parámetros. - """ - journal.send("Running endpoint 'Ver Estado de Transmisiones UFTP'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - - try: - journal.send("Running script 'getUFTPInfo.py'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - # Ejecutamos el script "getUFTPInfo.py", y almacenamos el resultado: - result = subprocess.run(['python3', f"{script_path}/getUFTPInfo.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 'getUFTPInfo.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 getUFTPInfo.py', 'desc':'Result OK (ReturnCode: 0)'}", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api") - return jsonify({ - "success": True, - "output": json.loads(result.stdout) - }), 200 - else: - journal.send(f"Script 'getUFTPInfo.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 getUFTPInfo.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: - if "exit status 1" in str(error_description): - journal.send("No UFTP active transmissions", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - journal.send("{'component':'ogRepo', 'severity':'WARNING', 'http_code':'400', 'operation':'Run script getUFTPInfo.py', 'desc':'Warning: No UFTP active transmissions'}", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api") - return jsonify({ - "success": False, - "exception": "No UFTP active transmissions" - }), 400 - else: - journal.send(f"Script 'getUFTPInfo.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 getUFTPInfo.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 - - -# --------------------------------------------------------- - - -# 18 - Endpoint "Cancelar Transmisión UDPcast" (SINCRONO): -@app.route("/ogrepository/v1/udpcast/images/", methods=['DELETE']) -def stop_udpcast(imageId): - """ Este endpoint cancela la transmisión UDPcast de la imagen que recibe como parámetro, finalizando el proceso "udp-sender" asociado. - Para ello, ejecuta el script "stopUDPcast.py", pasándole el nombre de la imagen. - """ - journal.send("Running endpoint 'Cancelar Transmisión UDPcast'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - - # Obtenemos el nombre y la extensión de la imagen: - param_dict = get_image_params(imageId, "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: - cmd = ['python3', f"{script_path}/stopUDPcast.py", f"{param_dict['name']}.{param_dict['extension']}"] - 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 stop_udpcast', 'desc':'Warning: Image not found'}", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api") - return jsonify({ - "success": False, - "error": "Image not found" - }), 400 - - try: - journal.send("Running script 'stopUDPcast.py'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - # Ejecutamos el script "stopUDPcast.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 una respuesta: - if result.returncode == 0: - journal.send("Script 'stopUDPcast.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 stopUDPcast.py', 'desc':'Result OK (ReturnCode: 0)'}", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api") - return jsonify({ - "success": True, - "output": "Image transmission canceled successfully" - }), 200 - else: - journal.send(f"Script 'stopUDPcast.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 stopUDPcast.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: - if "exit status 3" in str(error_description): - journal.send("No UDPCast active transmissions for specified image", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - journal.send("{'component':'ogRepo', 'severity':'WARNING', 'http_code':'400', 'operation':'Run script stopUDPcast.py', 'desc':'Warning: No UDPCast active transmissions for specified image'}", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api") - return jsonify({ - "success": False, - "exception": "No UDPCast active transmissions for specified image" - }), 400 - elif "exit status 4" in str(error_description): - journal.send("Script 'stopUDPcast.py' result KO (Unexpected error checking UDPcast transmissions)", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - journal.send("{'component':'ogRepo', 'severity':'ERROR', 'http_code':'500', 'operation':'Run script stopUDPcast.py', 'desc':'Result KO (Unexpected error checking UDPcast transmissions)'}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api") - return jsonify({ - "success": False, - "exception": "Unexpected error checking UDPcast transmissions" - }), 500 - elif "exit status 5" in str(error_description): - journal.send("Script 'stopUDPcast.py' result KO (Unexpected error finalizing UDPcast transmission)", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - journal.send("{'component':'ogRepo', 'severity':'ERROR', 'http_code':'500', 'operation':'Run script stopUDPcast.py', 'desc':'Result KO (Unexpected error finalizing UDPcast transmission)'}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api") - return jsonify({ - "success": False, - "exception": "Unexpected error finalizing UDPcast transmission" - }), 500 - else: - journal.send(f"Script 'stopUDPcast.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 stopUDPcast.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 - - -# --------------------------------------------------------- - - -# 19 - Endpoint "Cancelar Transmisión UFTP" (SINCRONO): -@app.route("/ogrepository/v1/uftp/images/", methods=['DELETE']) -def stop_uftp(imageId): - """ Este endpoint cancela la transmisión UFTP de la imagen que recibe como parámetro, finalizando el proceso "uftp" asociado. - Para ello, ejecuta el script "stopUFTP.py", pasándole el nombre de la imagen. - """ - journal.send("Running endpoint 'Cancelar Transmisión UFTP'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - - # Obtenemos el nombre y la extensión de la imagen: - param_dict = get_image_params(imageId, "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: - cmd = ['python3', f"{script_path}/stopUFTP.py", f"{param_dict['name']}.{param_dict['extension']}"] - 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 stop_uftp', 'desc':'Warning: Image not found'}", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api") - return jsonify({ - "success": False, - "error": "Image not found" - }), 400 - - try: - journal.send("Running script 'stopUFTP.py'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - # Ejecutamos el script "stopUFTP.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 una respuesta: - if result.returncode == 0: - journal.send("Script 'stopUFTP.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 stopUFTP.py', 'desc':'Result OK (ReturnCode: 0)'}", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api") - return jsonify({ - "success": True, - "output": "Image transmission canceled successfully" - }), 200 - else: - journal.send(f"Script 'stopUFTP.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 stopUFTP.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: - if "exit status 3" in str(error_description): - journal.send("No UFTP active transmissions for specified image", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - journal.send("{'component':'ogRepo', 'severity':'WARNING', 'http_code':'400', 'operation':'Run script stopUFTP.py', 'desc':'Warning: No UFTP active transmissions for specified image'}", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api") - return jsonify({ - "success": False, - "exception": "No UFTP active transmissions for specified image" - }), 400 - elif "exit status 4" in str(error_description): - journal.send("Script 'stopUFTP.py' result KO (Unexpected error checking UFTP transmissions)", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - journal.send("{'component':'ogRepo', 'severity':'ERROR', 'http_code':'500', 'operation':'Run script stopUFTP.py', 'desc':'Result KO (Unexpected error checking UFTP transmissions)'}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api") - return jsonify({ - "success": False, - "exception": "Unexpected error checking UFTP transmissions" - }), 500 - elif "exit status 5" in str(error_description): - journal.send("Script 'stopUFTP.py' result KO (Unexpected error finalizing UFTP transmission)", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - journal.send("{'component':'ogRepo', 'severity':'ERROR', 'http_code':'500', 'operation':'Run script stopUFTP.py', 'desc':'Result KO (Unexpected error finalizing UFTP transmission)'}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api") - return jsonify({ - "success": False, - "exception": "Unexpected error finalizing UFTP transmission" - }), 500 - else: - journal.send(f"Script 'stopUFTP.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 stopUFTP.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 - - -# --------------------------------------------------------- - - -# 20 - Endpoint "Cancelar Transmisiones P2P" (SINCRONO): -@app.route("/ogrepository/v1/p2p", methods=['DELETE']) -def stop_p2p(): - """ Este endpoint cancela las transmisiones P2P activas, finalizando los procesos "btlaunchmany.bittornado" (seeder) y "bttrack" (tracker). - Para ello, ejecuta el script "stopP2P.py", que no recibe parámetros. - """ - journal.send("Running endpoint 'Cancelar Transmisiones P2P'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - - try: - 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") - journal.send("{'component':'ogRepo', 'severity':'INFO', 'http_code':'200', 'operation':'Run script stopP2P.py', 'desc':'Result OK (ReturnCode: 0)'}", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api") - return jsonify({ - "success": True, - "output": "P2P transmissions canceled successfully" - }), 200 - else: - journal.send(f"Script 'stopP2P.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 stopP2P.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 'stopP2P.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 stopP2P.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 - - -# --------------------------------------------------------- - - -# 21 - Endpoint "Convertir imagen virtual a imagen OpenGnsys" (ASINCRONO): -@app.route("/ogrepository/v1/images/virtual", methods=['POST']) -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. - """ - journal.send("Running endpoint 'Convertir imagen virtual a imagen OpenGnsys'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - - # Almacenamos los parámetros enviados en el JSON, y extraemos el nombre y la extensión: - json_data = json.loads(request.data) - vm_image_name_full = json_data.get("virtual_image") - vm_image_name = vm_image_name_full.split('.')[0] - vm_extension = vm_image_name_full.split('.')[1] - filesystem = json_data.get("filesystem").lower() - - # Comprobamos si existe la imagen virtual, llamando a la función "check_file_exists": - vm_image_exists = check_file_exists(f"{vm_path}{vm_image_name_full}") - - # Si la imagen virtual no existe, devolvemos un error: - if vm_image_exists == False: - journal.send("Virtual image not found", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - journal.send("{'component':'ogRepo', 'severity':'WARNING', 'http_code':'400', 'operation':'Run endpoint convert_virtual_image', 'desc':'Warning: Virtual image not found'}", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api") - return jsonify({ - "success": False, - "exception": "Virtual image not found" - }), 400 - - # Comprobamos si ya existe una imagen "img" con el mismo nombre que la imagen virtual, llamando a la función "check_file_exists": - img_image_exists = check_file_exists(f"{repo_path}{vm_image_name}.img") - - # Si existe una imagen con el mismo nombre que la imagen virtual (salvo por la extensión), devolvemos un error: - if img_image_exists == True: - journal.send("There is an image with the same name as the virtual image", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - journal.send("{'component':'ogRepo', 'severity':'WARNING', 'http_code':'400', 'operation':'Run endpoint convert_virtual_image', 'desc':'Warning: There is an image with the same name as the virtual image'}", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api") - return jsonify({ - "success": False, - "exception": "There is an image with the same name as the virtual image" - }), 400 - - # Comprobamos si hay espacio suficiente en disco para convertir la imagen virtual (4 veces su tamaño): - enough_free_space = check_free_space(vm_image_name_full, vm_path) - - # Si no hay suficiente espacio libre en disco, devolvemos un error: - if enough_free_space == False: - journal.send("There is not enough free disk space", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - journal.send("{'component':'ogRepo', 'severity':'WARNING', 'http_code':'400', 'operation':'Run endpoint convert_virtual_image', 'desc':'Warning: There is not enough free disk space'}", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api") - return jsonify({ - "success": False, - "exception": "There is not enough free disk space" - }), 400 - - # Construimos la llamada al script: - cmd = ['python3', f"{script_path}/convertVMtoIMG.py", vm_image_name_full, filesystem] - - try: - journal.send("Running script 'convertVMtoIMG.py'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - # Ejecutamos el script "convertVMtoIMG.py" (con los parámetros almacenados), y almacenamos el resultado (pero sin esperar a que termine el proceso): - result = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding='UTF8') - - # Generamos el ID para identificar el trabajo asíncrono: - job_id = f"ConvertImageFromVirtual_{''.join(random.choice('0123456789abcdef') for char in range(8))}" - journal.send(f"JOB ID generated ({job_id})", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - - # Evaluamos el resultado de la ejecución, y devolvemos una respuesta: - 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") - threading.Thread(target=check_virtual_image_conversion, args=(vm_image_name, job_id,)).start() - - # Informamos que la imagen se está convirtiendo, y salimos del endpoint: - return jsonify({ - "success": True, - "output": "Converting virtual image...", - "job_id": job_id - }), 200 - else: - journal.send("Script 'convertVMtoIMG.py' result KO (Virtual image conversion failed)", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - journal.send("{'component':'ogRepo', 'severity':'ERROR', 'http_code':'500', 'operation':'Run script convertVMtoIMG.py', 'desc':'Result KO (Error: Virtual image conversion failed)'}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api") - return jsonify({ - "success": False, - "error": "Virtual image conversion failed" - }), 500 - except subprocess.CalledProcessError as error: - journal.send(f"Script 'convertVMtoIMG.py' result KO (Process Exception: {str(error)})", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - journal.send(f"{{'component':'ogRepo', 'severity':'ERROR', 'http_code':'500', 'operation':'Run script convertVMtoIMG.py', 'desc':'Result KO (Process Exception: {str(error)})'}}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api") - return jsonify({ - "success": False, - "process exception": str(error) - }), 500 - except Exception as error_description: - journal.send(f"Script 'convertVMtoIMG.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 convertVMtoIMG.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 - - -# --------------------------------------------------------- - - -# 22 - Endpoint "Convertir imagen OpenGnsys a imagen virtual" (ASINCRONO): -@app.route("/ogrepository/v1/images/virtual", methods=['PUT']) -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. - """ - 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,: - json_data = json.loads(request.data) - image_id = json_data.get("ID_img") - vm_extension = json_data.get("vm_extension").lower().lstrip('.') - - # 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_name_full = f"{param_dict['name']}.{param_dict['extension']}" - image_name = f"{param_dict['name']}" - cmd = ['python3', f"{script_path}/convertIMGtoVM.py", image_name_full, vm_extension] - 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 convert_image_to_virtual', '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 virtual exportada con el mismo nombre que la imagen "img" y la extensión especificada, llamando a la función "check_file_exists": - vm_image_exists = check_file_exists(f"{vm_path}export/{image_name}.{vm_extension}") - - # Si existe una imagen con el mismo nombre que la imagen virtual (salvo por la extensión), devolvemos un error: - if vm_image_exists == True: - journal.send("There is an exported virtual image with the same name as the image", 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 exported virtual image with the same name as the image'}", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api") - return jsonify({ - "success": False, - "exception": "There is an exported virtual image with the same name as the image" - }), 400 - - # Comprobamos si hay espacio suficiente en disco para convertir la imagen "img" a virtual (4 veces su tamaño): - enough_free_space = check_free_space(image_name_full, repo_path) - - # Si no hay suficiente espacio libre en disco, devolvemos un error: - if enough_free_space == False: - journal.send("There is not enough free disk space", 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 not enough free disk space'}", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api") - return jsonify({ - "success": False, - "exception": "There is not enough free disk space" - }), 400 - - try: - journal.send("Running script 'convertIMGtoVM.py'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - # Ejecutamos el script "convertIMGtoVM.py" (con los parámetros almacenados), y almacenamos el resultado (pero sin esperar a que termine el proceso): - result = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding='UTF8') - - # Generamos el ID para identificar el trabajo asíncrono: - job_id = f"ConvertImageToVirtual_{''.join(random.choice('0123456789abcdef') for char in range(8))}" - journal.send(f"JOB ID generated ({job_id})", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - - # Evaluamos el resultado de la ejecución, y devolvemos una respuesta: - 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") - threading.Thread(target=check_virtual_image_reconversion, args=(image_name, vm_extension, job_id,)).start() - - # Informamos que la imagen se está convirtiendo, y salimos del endpoint: - return jsonify({ - "success": True, - "output": "Converting image to virtual...", - "job_id": job_id - }), 200 - else: - journal.send("Script 'convertIMGtoVM.py' result KO (Image conversion to virtual failed)", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - journal.send("{'component':'ogRepo', 'severity':'ERROR', 'http_code':'500', 'operation':'Run script convertIMGtoVM.py', 'desc':'Result KO (Error: Image conversion to virtual failed)'}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api") - return jsonify({ - "success": False, - "error": "Image conversion to virtual failed" - }), 500 - except subprocess.CalledProcessError as error: - journal.send(f"Script 'convertIMGtoVM.py' result KO (Process Exception: {str(error)})", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - journal.send(f"{{'component':'ogRepo', 'severity':'ERROR', 'http_code':'500', 'operation':'Run script convertIMGtoVM.py', 'desc':'Result KO (Process Exception: {str(error)})'}}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api") - return jsonify({ - "success": False, - "process exception": str(error) - }), 500 - except Exception as error_description: - journal.send(f"Script 'convertIMGtoVM.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 convertIMGtoVM.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_server/blueprints/repo_api.yaml b/api_server/blueprints/repo_api.yaml deleted file mode 100644 index 54cd2af..0000000 --- a/api_server/blueprints/repo_api.yaml +++ /dev/null @@ -1,1880 +0,0 @@ ---- -swagger: "2.0" -info: - title: "ogRepository API" - version: "1.0" - description: | - --- - -# ----------------------------------------------------------------------------------------------------------- - -# Esto hace que el Swagger se ordene por los tags (apartados), de la forma especificada: -tags: - - name: "Estado de ogRepository" - - name: "Información de Imágenes" - - name: "Eliminar y Recuperar Imágenes" - - name: "Transferencia de Imágenes (UDPcast)" - - name: "Transferencia de Imágenes (UFTP)" - - name: "Transferencia de Imágenes (P2P)" - - name: "Transferencia entre Repositorios y Backup" - - name: "Importar y Exportar Máquinas Virtuales" - - name: "Varios" - - -# ----------------------------------------------------------------------------------------------------------- -# Apartado "Estado de ogRepository" -# ----------------------------------------------------------------------------------------------------------- - -paths: - /ogrepository/v1/status: - get: - 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. - tags: - - "Estado de ogRepository" - responses: - "200": - description: "La información de estado se obtuvo exitosamente." - schema: - type: object - properties: - success: - type: boolean - example: true - output: - type: object - properties: - oggit: - type: object - properties: - installed: - type: string - example: "True" - cpu: - type: object - properties: - used_percentage: - type: string - example: "35%" - ram: - type: object - properties: - total: - type: string - example: "7.8GB" - used: - type: string - example: "0.3GB" - available: - type: string - example: "7.2GB" - used_percentage: - type: string - example: "7%" - disk: - type: object - properties: - total: - type: string - example: "11.7GB" - used: - type: string - example: "7.7GB" - available: - type: string - example: "3.4GB" - used_percentage: - type: string - example: "69%" - services: - type: object - properties: - ssh: - type: string - example: "active" - smbd: - type: string - example: "active" - rsync: - type: string - example: "active" - processes: - type: object - properties: - udp-sender: - type: string - example: "stopped" - uftp: - type: string - example: "stopped" - bttrack: - type: string - example: "stopped" - btlaunchmany.bittornado: - type: string - example: "stopped" - "500 (Error)": - description: "Error al consultar y/o devolver la información de estado." - schema: - type: object - properties: - success: - type: boolean - example: false - error: - type: string - example: "(Error description)" - "500 (Exception)": - description: "Excepción inesperada al consultar y/o devolver la información de estado." - schema: - type: object - properties: - success: - type: boolean - example: false - exception: - type: string - example: "(Exception description)" - -# ----------------------------------------------------------------------------------------------------------- -# 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" - responses: - "200": - description: "La actualización de la información de las imágenes se realizó exitosamente." - schema: - type: object - properties: - success: - type: boolean - example: true - output: - type: string - example: "Repository info updated successfully" - "500 (Error)": - description: "Error al actualizar la información de las imágenes." - schema: - type: object - properties: - success: - type: boolean - example: false - error: - type: string - example: "(Error description)" - "500 (Exception)": - description: "Excepción inesperada al actualizar la información de las imágenes." - schema: - type: object - properties: - success: - type: boolean - example: false - exception: - type: string - example: "(Exception description)" - -# ----------------------------------------------------------------------------------------------------------- - - #/ogrepository/v1/images: - 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. - 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" - responses: - "200": - description: "La información de las imágenes se obtuvo exitosamente." - schema: - type: object - properties: - success: - type: boolean - example: true - output: - type: object - properties: - REPOSITORY: - type: object - properties: - directory: - type: string - example: "/opt/opengnsys/ogrepository/images" - images: - type: array - items: - type: object - properties: - name: - type: string - example: "Ubuntu24" - type: - type: string - example: "img" - clientname: - type: string - example: "Ubuntu24_clientname" - clonator: - type: string - example: "partclone" - compressor: - type: string - example: "lzop" - filesystem: - type: string - example: "EXTFS" - datasize: - type: integer - example: 9859634200000 - size: - type: integer - example: 4505673214 - sum: - type: string - example: "065a933c780ab1aaa044435ad5d4bf87" - fullsum: - type: string - example: "33575b9070e4a8043371b8c6ae52b80e" - TRASH: - type: object - properties: - directory: - type: string - example: "/opt/opengnsys/ogrepository/images_trash" - images: - type: array - items: - type: object - properties: - name: - type: string - example: "Windows10" - type: - type: string - example: "img" - clientname: - type: string - example: "Windows10_clientname" - clonator: - type: string - example: "partclone" - compressor: - type: string - example: "lzop" - filesystem: - type: string - example: "NTFS" - datasize: - type: integer - example: 8912896000000 - size: - type: integer - example: 3803794535 - sum: - type: string - example: "081a933c780ab1aaa044435ad5d4bf56" - fullsum: - type: string - example: "22735b9070e4a8043371b8c6ae52b90d" - "500 (Error)": - description: "Error al consultar y/o devolver la información de las imágenes." - schema: - type: object - properties: - success: - type: boolean - example: false - error: - type: string - example: "(Error description)" - "500 (Exception)": - description: "Excepción inesperada al consultar y/o devolver la información de las imágenes." - schema: - type: object - properties: - success: - type: boolean - example: false - exception: - type: string - example: "(Exception description)" - -# ----------------------------------------------------------------------------------------------------------- - - /ogrepository/v1/images/{imageId}: - get: - summary: "Obtener Información de una Imagen concreta" - description: | - Este endpoint devuelve información de la imagen especificada mediante su ID, en formato JSON. - Utiliza el script "**getRepoInfo.py**" que recibe como parámetro el nombre de la imagen (con extensión), que a su vez llama al script "**updateRepoInfo.py**", para actualizar previamente la información del repositorio. - La imagen puede estar en el archivo "**repoinfo.json**" (si está almacenada en el repositorio) o en "**trashinfo.json**" (si está en la papelera). - tags: - - "Información de Imágenes" - parameters: - - name: imageId - in: path - required: true - type: string - description: "Identificador de la imagen (correspondiente al contenido del archivo 'full.sum')" - responses: - "200": - description: "La información de la imagen se obtuvo exitosamente." - schema: - type: object - properties: - success: - type: boolean - example: true - output: - type: object - properties: - directory: - type: string - example: "/opt/opengnsys/ogrepository/images" - images: - type: array - items: - type: object - properties: - name: - type: string - example: "Ubuntu24" - type: - type: string - example: "img" - clientname: - type: string - example: "Ubuntu24_clientname" - clonator: - type: string - example: "partclone" - compressor: - type: string - example: "lzop" - filesystem: - type: string - example: "EXTFS" - datasize: - type: integer - example: 9859634200000 - size: - type: integer - example: 4505673214 - sum: - type: string - example: "065a933c780ab1aaa044435ad5d4bf87" - fullsum: - type: string - example: "33575b9070e4a8043371b8c6ae52b80e" - "400": - description: "No se ha encontrado la imagen especificada." - schema: - type: object - properties: - success: - type: boolean - example: false - error: - type: string - example: "Image not found" - "500 (Error)": - description: "Error al consultar y/o devolver la información de la imagen." - schema: - type: object - properties: - success: - type: boolean - example: false - error: - type: string - example: "(Error description)" - "500 (Exception)": - description: "Excepción inesperada al consultar y/o devolver la información de la imagen." - schema: - type: object - properties: - success: - type: boolean - example: false - exception: - type: string - example: "(Exception description)" - -# ----------------------------------------------------------------------------------------------------------- - - /ogrepository/v1/status/images/{imageId}: - get: - summary: "Chequear Integridad de Imagen" - description: | - Este endpoint comprueba la integridad de la imagen especificada como parámetro, comparando el tamaño y el hash MD5 del último MB con los valores almacenados en los archivos ".size" y ".sum". - Utiliza el script "**checkImage.py**", que recibe el nombre de la imagen (con extensión) como parámetro. - tags: - - "Información de Imágenes" - parameters: - - name: imageId - in: path - required: true - type: string - description: "Identificador de la imagen (correspondiente al contenido del archivo 'full.sum')" - responses: - "200 (Check OK)": - description: "La imagen se ha chequeado exitosamente." - schema: - type: object - properties: - success: - type: boolean - example: true - output: - type: string - example: "Image file passed the Integrity Check correctly" - "200 (Check KO)": - description: "La imagen se ha chequeado exitosamente, pero no ha pasado el test de integridad." - schema: - type: object - properties: - success: - type: boolean - example: true - output: - type: string - example: "Image file didn't pass the Integrity Check" - "400": - description: "No se ha encontrado la imagen especificada." - schema: - type: object - properties: - success: - type: boolean - example: false - error: - type: string - example: "Image not found (inexistent or deleted)" - "500 (Error)": - description: "Error al chequear la integridad de la imagen." - schema: - type: object - properties: - success: - type: boolean - example: false - error: - type: string - example: "(Error description)" - "500 (Exception)": - description: "Excepción inesperada al chequear la integridad de la imagen." - schema: - type: object - properties: - success: - type: boolean - example: false - exception: - type: string - example: "(Exception description)" - -# ----------------------------------------------------------------------------------------------------------- -# Apartado "Eliminar y Recuperar Imágenes" -# ----------------------------------------------------------------------------------------------------------- - - /ogrepository/v1/images/{imageId}?method={method}: - delete: - summary: "Eliminar una Imagen" - description: | - Este endpoint elimina la imagen especificada como parámetro (y todos sus archivos asociados), moviéndolos a la papelera o eliminándolos permanentemente (dependiendo del parámetro "method"). - Utiliza el script "**deleteImage.py**" que recibe el nombre de la imagen (con extensión) como primer parámetro, y opcionalmente el parámetro "-p" (para eliminación permanente), que a su vez llama al script "**updateRepoInfo.py**", para actualizar la información del repositorio. - tags: - - "Eliminar y Recuperar Imágenes" - parameters: - - name: imageId - in: path - required: true - type: string - description: "Identificador de la imagen (correspondiente al contenido del archivo 'full.sum')" - - name: method - in: query - required: true - type: string - description: "Método de eliminación (puede ser 'trash', para enviar la imagen a la papelera, o 'permanent', para eliminarla definitivamente)" - responses: - "200": - description: "La imagen se eliminó exitosamente." - schema: - type: object - properties: - success: - type: boolean - example: true - output: - type: string - example: "Image deleted successfully" - "400": - description: "No se ha encontrado la imagen especificada." - schema: - type: object - properties: - success: - type: boolean - example: false - error: - type: string - example: "Image not found (inexistent or deleted)" - "500 (Error)": - description: "Error al eliminar la imagen." - schema: - type: object - properties: - success: - type: boolean - example: false - error: - type: string - example: "(Error description)" - "500 (Exception)": - description: "Excepción inesperada al eliminar la imagen." - schema: - type: object - properties: - success: - type: boolean - example: false - exception: - type: string - example: "(Exception description)" - -# ----------------------------------------------------------------------------------------------------------- - - /ogrepository/v1/trash/images: - post: - summary: "Recuperar una Imagen" - description: | - Este endpoint recupera la imagen especificada, moviéndola desde la papelera al repositorio de imágenes. - Utiliza el script "**recoverImage.py**", que recibe el nombre de la imagen (con extensión), que a su vez llama al script "**updateRepoInfo.py**", para actualizar la información del repositorio. - tags: - - "Eliminar y Recuperar Imágenes" - parameters: - - name: JSON - in: body - required: true - description: | - * **ID_img** - Identificador de la imagen, correspondiente al contenido del archivo 'full.sum' - schema: - type: object - properties: - ID_img: - type: string - example: "22735b9070e4a8043371b8c6ae52b90d" - responses: - "200": - description: "La imagen se recuperó exitosamente." - schema: - type: object - properties: - success: - type: boolean - example: true - output: - type: string - example: "Image recovered successfully" - "400": - description: "No se ha encontrado la imagen especificada." - schema: - type: object - properties: - success: - type: boolean - example: false - error: - type: string - example: "Image not found (inexistent or recovered previously)" - "500 (Error)": - description: "Error al recuperar la imagen." - schema: - type: object - properties: - success: - type: boolean - example: false - error: - type: string - example: "(Error description)" - "500 (Exception)": - description: "Excepción inesperada al recuperar la imagen." - schema: - type: object - properties: - success: - type: boolean - example: false - exception: - type: string - example: "(Exception description)" - -# ----------------------------------------------------------------------------------------------------------- - - /ogrepository/v1/trash/images/{imageId}: - delete: - summary: "Eliminar una Imagen de la Papelera" - description: | - Este endpoint elimina permanentemente la imagen especificada, desde la papelera. - Utiliza el script "**deleteTrashImage.py**", que recibe el nombre de la imagen (con extensión), que a su vez llama al script "**updateTrashInfo.py**", para actualizar la información de la papelera. - tags: - - "Eliminar y Recuperar Imágenes" - parameters: - - name: imageId - in: path - required: true - type: string - description: "Identificador de la imagen (correspondiente al contenido del archivo 'full.sum')" - responses: - "200": - description: "La imagen se eliminó exitosamente." - schema: - type: object - properties: - success: - type: boolean - example: true - output: - type: string - example: "Image deleted successfully" - "400": - description: "No se ha encontrado la imagen especificada en la papelera." - schema: - type: object - properties: - success: - type: boolean - example: false - error: - type: string - example: "Image not found at trash" - "500 (Error)": - description: "Error al eliminar la imagen de la papelera." - schema: - type: object - properties: - success: - type: boolean - example: false - error: - type: string - example: "(Error description)" - "500 (Exception)": - description: "Excepción inesperada al eliminar la imagen de la papelera." - schema: - type: object - properties: - success: - type: boolean - example: false - exception: - type: string - example: "(Exception description)" - -# ----------------------------------------------------------------------------------------------------------- -# Apartado "Transferencia de Imágenes (UDPcast)" -# ----------------------------------------------------------------------------------------------------------- - - /ogrepository/v1/udpcast: - post: - summary: "Enviar una Imagen mediante UDPcast" - description: | - Este endpoint envía la imagen especificada a través de UDPcast, utilizando el script "**sendFileMcast.py**". - Recibe la imagen y los parámetros de configuración de transferencia, que son usados para construir la cadena de parámetros que se envía al script. - - **NOTA**: Este endpoint es asíncrono, ya que puede tardar mucho tiempo, por lo que solo informa de que la imagen se está enviando, y abre un proceso paralelo (pero no avisa a ogCore de su finalización, porque no puede comprobar cuando acaba la tarea de restauración de la imagen). - tags: - - "Transferencia de Imágenes (UDPcast)" - parameters: - - name: JSON - in: body - required: true - description: | - * **ID_img** - Identificador de la imagen, correspondiente al contenido del archivo 'full.sum' - * **port** - Puerto Multicast - * **method** - Modalidad half-duplex o full-duplex ("half" o "full") - * **ip** - IP Multicast - * **bitrate** - Velocidad de transmisión, en Mbps - * **nclients** - Número minimo de clientes - * **maxtime** - Tiempo máximo de espera - schema: - type: object - properties: - ID_img: - type: string - example: "22735b9070e4a8043371b8c6ae52b90d" - port: - type: string - description: "Puerto Multicast a utilizar para la transmisión" - example: "9000" - method: - type: string - description: "Modalidad de transmisión (puede ser 'half' o 'full' para half-duplex o full-duplex)" - example: "full" - ip: - type: string - description: "IP Multicast a la que se enviará la imagen" - example: "239.194.17.2" - bitrate: - type: string - description: "Velocidad de transmisión en Mbps" - example: "70M" - nclients: - type: integer - description: "Número mínimo de clientes" - example: 20 - maxtime: - type: integer - description: "Tiempo máximo de espera en segundos" - example: 120 - responses: - "200": - description: "La imagen se está enviando mediante UDPcast." - schema: - type: object - properties: - success: - type: boolean - example: true - output: - type: string - example: "Sending image.." - "400": - description: "No se ha encontrado la imagen especificada." - schema: - type: object - properties: - success: - type: boolean - example: false - error: - type: string - example: "Image not found" - "500 (Error)": - description: "Error al enviar la imagen mediante UDPcast." - schema: - type: object - properties: - success: - type: boolean - example: false - error: - type: string - example: "Image send failed" - "500 (Exception)": - description: "Excepción inesperada al enviar la imagen mediante UDPcast." - schema: - type: object - properties: - success: - type: boolean - example: false - exception: - type: string - example: "(Exception description)" - -# ----------------------------------------------------------------------------------------------------------- - - #/ogrepository/v1/udpcast: - get: - summary: "Ver Estado de Transmisiones UDPcast" - description: | - Este endpoint devuelve información sobre los procesos activos de "**udp-sender**" en formato JSON, permitiendo comprobar las transferencias UDPcast activas. - Utiliza el script "**getUDPcastInfo.py**" para obtener esta información. - tags: - - "Transferencia de Imágenes (UDPcast)" - responses: - "200": - description: "La información de las transmisiones UDPcast activas se obtuvo exitosamente." - schema: - type: object - properties: - success: - type: boolean - example: true - output: - type: object - additionalProperties: - type: object - properties: - image_id: - type: string - example: "22735b9070e4a8043371b8c6ae52b90d" - image_name: - type: string - example: "Ubuntu20.img" - "400": - description: "No se han encontrado transmisiones UDPcast activas." - schema: - type: object - properties: - success: - type: boolean - example: false - exception: - type: string - example: "No UDPCast active transmissions" - "500 (Error)": - description: "Error al comprobar las transmisiones UDPcast activas." - schema: - type: object - properties: - success: - type: boolean - example: false - error: - type: string - example: "(Error description)" - "500 (Exception)": - description: "Excepción inesperada al comprobar las transmisiones UDPcast activas." - schema: - type: object - properties: - success: - type: boolean - example: false - exception: - type: string - example: "(Exception description)" - -# ----------------------------------------------------------------------------------------------------------- - - /ogrepository/v1/udpcast/images/{imageId}: - delete: - summary: "Cancelar Transmisión UDPcast" - description: | - Este endpoint cancela la transmisión UDPcast activa de la imagen especificada, deteniendo el proceso "**udp-sender**" asociado. - Utiliza el script "**stopUDPcast.py**" para finalizar la transmisión. - tags: - - "Transferencia de Imágenes (UDPcast)" - parameters: - - name: imageId - in: path - required: true - type: string - description: "Identificador de la imagen (correspondiente al contenido del archivo 'full.sum')" - responses: - "200": - description: "La transmisión UDPcast se ha cancelado exitosamente." - schema: - type: object - properties: - success: - type: boolean - example: true - output: - type: string - example: "Image transmission canceled successfully" - "400 (Image not found)": - description: "No se ha encontrado la imagen especificada." - schema: - type: object - properties: - success: - type: boolean - example: false - exception: - type: string - example: "Image not found" - "400 (No transmissions for image)": - description: "No hay transmisiones UDPcast activas para la imagen especificada." - schema: - type: object - properties: - success: - type: boolean - example: false - exception: - type: string - example: "No UDPCast active transmissions for specified image" - "500 (Error)": - description: "Error al cancelar la transmisión UDPcast." - schema: - type: object - properties: - success: - type: boolean - example: false - error: - type: string - example: "(Error description)" - "500 (Check Exception)": - description: "Error al verificar las transmisiones UDPcast activas." - schema: - type: object - properties: - success: - type: boolean - example: false - exception: - type: string - example: "Unexpected error checking UDPcast transmissions" - "500 (Finalize Exception)": - description: "Error inesperado al finalizar la transmisión UDPcast." - schema: - type: object - properties: - success: - type: boolean - example: false - exception: - type: string - example: "Unexpected error finalizing UDPcast transmission" - "500 (General Exception)": - description: "Excepción inesperada al cancelar la transmisión UDPcast." - schema: - type: object - properties: - success: - type: boolean - example: false - exception: - type: string - example: "(Exception description)" - -# ----------------------------------------------------------------------------------------------------------- -# Apartado "Transferencia de Imágenes (UFTP)" -# ----------------------------------------------------------------------------------------------------------- - - /ogrepository/v1/uftp: - post: - summary: "Enviar una Imagen mediante UFTP" - description: | - Este endpoint envía una imagen especificada a través de UFTP, utilizando el script "**sendFileUFTP.py**". - Requiere que los clientes ogLive estén previamente en escucha con un daemon "UFTPD", ejecutando el script "**listenUFTPD.py**". - Recibe la imagen y los parámetros de configuración de transferencia, que son usados para construir la cadena de parámetros que se envía al script. - - **NOTA**: Este endpoint es asíncrono, ya que puede tardar mucho tiempo, por lo que solo informa de que la imagen se está enviando, y abre un proceso paralelo (pero no avisa a ogCore de su finalización, porque no puede comprobar cuando acaba la tarea de restauración de la imagen). - tags: - - "Transferencia de Imágenes (UFTP)" - parameters: - - name: JSON - in: body - required: true - description: | - * **ID_img** - Identificador de la imagen, correspondiente al contenido del archivo 'full.sum' - * **port** - Puerto Multicast - * **ip** - IP Unicast/Multicast), - * **bitrate** - Velocidad de transmisión, con 'K' para Kbps, 'M' para Mbps o 'G' para Gbps - schema: - type: object - properties: - ID_img: - type: string - example: "22735b9070e4a8043371b8c6ae52b90d" - port: - type: string - description: "Puerto para la transmisión UFTP" - example: "9000" - ip: - type: string - description: "IP Unicast o Multicast para la transmisión" - example: "239.194.17.2" - bitrate: - type: string - description: "Velocidad de transmisión (con 'K' para Kbps, 'M' para Mbps, o 'G' para Gbps)" - example: "1G" - responses: - "200": - description: "La imagen se está enviando mediante UFTP." - schema: - type: object - properties: - success: - type: boolean - example: true - output: - type: string - example: "Sending image..." - "400": - description: "No se ha encontrado la imagen especificada." - schema: - type: object - properties: - success: - type: boolean - example: false - error: - type: string - example: "Image not found" - "500 (Error)": - description: "Error al enviar la imagen mediante UFTP." - schema: - type: object - properties: - success: - type: boolean - example: false - error: - type: string - example: "Image send failed" - "500 (Exception)": - description: "Excepción inesperada al enviar la imagen mediante UFTP." - schema: - type: object - properties: - success: - type: boolean - example: false - exception: - type: string - example: "(Exception description)" - -# ----------------------------------------------------------------------------------------------------------- - - #/ogrepository/v1/uftp: - get: - summary: "Ver Estado de Transmisiones UFTP" - description: | - Este endpoint devuelve información sobre los procesos activos de "**uftp**" en formato JSON, permitiendo comprobar las transferencias UFTP activas. - Utiliza el script "**getUFTPInfo.py**" para obtener esta información. - tags: - - "Transferencia de Imágenes (UFTP)" - responses: - "200": - description: "La información de las transmisiones UFTP activas se obtuvo exitosamente." - schema: - type: object - properties: - success: - type: boolean - example: true - output: - type: object - additionalProperties: - type: object - properties: - image_id: - type: string - example: "22735b9070e4a8043371b8c6ae52b90d" - image_name: - type: string - example: "Ubuntu20.img" - "400": - description: "No se han encontrado transmisiones UFTP activas." - schema: - type: object - properties: - success: - type: boolean - example: false - exception: - type: string - example: "No UFTP active transmissions" - "500 (Error)": - description: "Error al comprobar las transmisiones UFTP activas." - schema: - type: object - properties: - success: - type: boolean - example: false - error: - type: string - example: "(Error description)" - "500 (Exception)": - description: "Excepción inesperada al comprobar las transmisiones UFTP activas." - schema: - type: object - properties: - success: - type: boolean - example: false - exception: - type: string - example: "(Exception description)" - -# ----------------------------------------------------------------------------------------------------------- - - /ogrepository/v1/uftp/images/{imageId}: - delete: - summary: "Cancelar Transmisión UFTP" - description: | - Este endpoint cancela la transmisión UFTP activa de una imagen especificada, deteniendo el proceso "**uftp**" asociado. - Utiliza el script "**stopUFTP.py**" para finalizar la transmisión. - tags: - - "Transferencia de Imágenes (UFTP)" - parameters: - - name: imageId - in: path - required: true - type: string - description: "Identificador de la imagen (correspondiente al contenido del archivo 'full.sum')" - responses: - "200": - description: "La transmisión UFTP se ha cancelado exitosamente." - schema: - type: object - properties: - success: - type: boolean - example: true - output: - type: string - example: "Image transmission canceled successfully" - "400 (Image not found)": - description: "No se ha encontrado la imagen especificada." - schema: - type: object - properties: - success: - type: boolean - example: false - exception: - type: string - example: "Image not found" - "400 (No transmissions for image)": - description: "No hay transmisiones UFTP activas para la imagen especificada." - schema: - type: object - properties: - success: - type: boolean - example: false - exception: - type: string - example: "No UFTP active transmissions for specified image" - "500 (Error)": - description: "Error al cancelar la transmisión UFTP." - schema: - type: object - properties: - success: - type: boolean - example: false - error: - type: string - example: "(Error description)" - "500 (Check Exception)": - description: "Error al verificar las transmisiones UFTP activas." - schema: - type: object - properties: - success: - type: boolean - example: false - exception: - type: string - example: "Unexpected error checking UFTP transmissions" - "500 (Finalize Exception)": - description: "Error inesperado al finalizar la transmisión UFTP." - schema: - type: object - properties: - success: - type: boolean - example: false - exception: - type: string - example: "Unexpected error finalizing UFTP transmission" - "500 (General Exception)": - description: "Excepción inesperada al cancelar la transmisión UFTP." - schema: - type: object - properties: - success: - type: boolean - example: false - exception: - type: string - example: "(Exception description)" - -# ----------------------------------------------------------------------------------------------------------- -# Apartado "Transferencia de Imágenes (P2P)" -# ----------------------------------------------------------------------------------------------------------- - - /ogrepository/v1/p2p: - post: - summary: "Enviar una Imagen mediante P2P" - description: | - Este endpoint inicia el tracker y el seeder de torrents para enviar una imagen especificada mediante P2P. - Utiliza los scripts "**runTorrentTracker.py**" y "**runTorrentSeeder.py**" para iniciar el tracker y el seeder en el directorio de imágenes. - - **NOTA**: Este endpoint es asíncrono, ya que puede tardar mucho tiempo, por lo que solo informa de que la imagen se está enviando, y abre un proceso paralelo (pero no avisa a ogCore de su finalización, porque no puede comprobar cuando acaba la tarea de restauración de la imagen). - tags: - - "Transferencia de Imágenes (P2P)" - parameters: - - name: JSON - in: body - required: true - description: | - * **ID_img** - Identificador de la imagen, correspondiente al contenido del archivo 'full.sum' - schema: - type: object - properties: - ID_img: - type: string - example: "22735b9070e4a8043371b8c6ae52b90d" - responses: - "200": - description: "La imagen se está enviando mediante P2P." - schema: - type: object - properties: - success: - type: boolean - example: true - output: - type: string - example: "Tracker and Seeder serving image correctly" - "400": - description: "No se ha encontrado la imagen especificada." - schema: - type: object - properties: - success: - type: boolean - example: false - error: - type: string - example: "Image not found" - "500": - description: "Error al enviar la imagen mediante P2P." - schema: - type: object - properties: - success: - type: boolean - example: false - error: - type: string - example: "Tracker or Seeder (or both) not running" - -# ----------------------------------------------------------------------------------------------------------- - - #/ogrepository/v1/p2p: - delete: - summary: "Cancelar Transmisiones P2P" - description: | - Este endpoint cancela todas las transmisiones P2P activas, finalizando los procesos "**bttrack**" (tracker) y "**btlaunchmany.bittornado**" (seeder). - Utiliza el script "**stopP2P.py**" para detener las transmisiones P2P activas en el servidor. - tags: - - "Transferencia de Imágenes (P2P)" - responses: - "200": - description: "Las transmisiones P2P se han cancelado exitosamente." - schema: - type: object - properties: - success: - type: boolean - example: true - output: - type: string - example: "P2P transmissions canceled successfully" - "500 (Error)": - description: "Error al cancelar las transmisiones P2P." - schema: - type: object - properties: - success: - type: boolean - example: false - error: - type: string - example: "(Error description)" - "500 (Exception)": - description: "Excepción inesperada al cancelar las transmisiones P2P." - schema: - type: object - properties: - success: - type: boolean - example: false - exception: - type: string - example: "(Exception description)" - -# ----------------------------------------------------------------------------------------------------------- -# Apartado "Transferencia entre Repositorios y Backup" -# ----------------------------------------------------------------------------------------------------------- - - /ogrepository/v1/repo/images: - post: - summary: "Transferir una Imagen entre Repositorios" - description: | - 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" - parameters: - - name: JSON - in: body - required: true - description: | - * **image** - Nombre de la imagen, con extensión - * **repo_ip** - Dirección IP del servidor remoto - * **user** - Usuario con el que conectar al servidor remoto - schema: - type: object - properties: - image: - type: string - example: "Windows10.img" - repo_ip: - type: string - description: "Dirección IP del repositorio remoto" - example: "192.168.56.100" - user: - type: string - description: "Usuario para acceder al repositorio remoto" - example: "user_name" - responses: - "200": - description: "La imagen se está transfiriendo." - schema: - type: object - properties: - success: - type: boolean - example: true - output: - type: string - example: "Importing image..." - "400 (Connection fail)": - description: "Error de conexión con el servidor remoto." - schema: - type: object - properties: - success: - type: boolean - example: false - exception: - type: string - example: "Can't connect to remote server" - "400 (Image not found)": - description: "No se ha encontrado la imagen remota." - schema: - type: object - properties: - success: - type: boolean - example: false - exception: - type: string - example: "Remote image not found" - "400 (Image locked)": - description: "La imagen remota está bloqueada." - schema: - type: object - properties: - success: - type: boolean - example: false - exception: - type: string - example: "Remote image is locked" - "500": - description: "Error al importar la imagen." - schema: - type: object - properties: - success: - type: boolean - example: false - error: - type: string - example: "Image import failed" - "500 (Error)": - description: "Error al importar la imagen." - schema: - type: object - properties: - success: - type: boolean - example: false - error: - type: string - example: "(Error description)" - "500 (Exception)": - description: "Excepción inesperada al importar la imagen." - schema: - type: object - properties: - success: - type: boolean - example: false - exception: - type: string - example: "(Exception description)" - -# ----------------------------------------------------------------------------------------------------------- - - #/ogrepository/v1/repo/images: - put: - summary: "Hacer Backup de una Imagen" - description: | - Este endpoint hace un backup de la imagen especificada, exportándola desde el servidor local a un equipo remoto (que no tiene por qué tener OpenGnsys instalado, pero si conectividad SSH). - Utiliza el script "**backupImage.py**", que recibe como parámetros el nombre de la imagen, la IP o hostname del servidor remoto, el usuario con el que conectar al servidor remoto, y la ruta remota en la que guardar el backup. - - **NOTA**: Este endpoint es asíncrono, ya que puede tardar mucho tiempo, por lo que solo informa de que la imagen se está copiando, 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" - parameters: - - name: JSON - in: body - required: true - description: | - * **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 - schema: - type: object - properties: - ID_img: - type: string - example: "22735b9070e4a8043371b8c6ae52b90d" - repo_ip: - type: string - description: "Dirección IP del repositorio remoto" - example: "192.168.56.100" - user: - type: string - description: "Usuario para acceder al repositorio remoto" - example: "user_name" - remote_path: - type: string - description: "Ruta remota en la que copiar la imagen" - example: "/home/opengnsys" - responses: - "200": - description: "Se está haciendo backup de la imagen." - schema: - type: object - properties: - success: - type: boolean - example: true - output: - type: string - example: "Making image backup..." - "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 (Image locked)": - description: "La imagen está bloqueada." - schema: - type: object - properties: - success: - type: boolean - example: false - exception: - type: string - example: "Image is locked" - "400 (Connection fail)": - description: "Error de conexión con el equipo remoto." - schema: - type: object - properties: - success: - type: boolean - example: false - exception: - type: string - example: "Can't connect to remote host" - "500 (Error)": - description: "Error al copiar la imagen." - schema: - type: object - properties: - success: - type: boolean - example: false - error: - type: string - example: "(Error description)" - "500 (Exception)": - description: "Excepción inesperada al copiar la imagen." - schema: - type: object - properties: - success: - type: boolean - example: false - exception: - type: string - example: "(Exception description)" - - -# ----------------------------------------------------------------------------------------------------------- -# Apartado "Importar y Exportar a Máquinas Virtuales" -# ----------------------------------------------------------------------------------------------------------- - - /ogrepository/v1/images/virtual: - 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". - - **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" - 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" - schema: - type: object - properties: - virtual_image: - type: string - example: "UbuntuVM.vdi" - filesystem: - type: string - example: "ext4" - responses: - "200": - description: "La imagen virtual se está convirtiendo." - schema: - type: object - properties: - success: - type: boolean - example: true - output: - type: string - example: "Converting virtual image..." - "400 (Virtual image not found)": - description: "No se ha encontrado la imagen virtual." - schema: - type: object - properties: - success: - type: boolean - example: false - exception: - type: string - example: "Virtual image not found" - "400 (Name incorrect)": - description: "Ya existe una imagen con el mismo nombre que la imagen virtual." - schema: - type: object - properties: - success: - type: boolean - example: false - exception: - type: string - example: "There is an image with the same name as the virtual image" - "400 (No disk space)": - description: "No hay espacio suficiente en disco." - schema: - type: object - properties: - success: - type: boolean - example: false - exception: - type: string - example: "There is not enough free disk space" - "500": - description: "Error al convertir la imagen virtual." - schema: - type: object - properties: - success: - type: boolean - example: false - error: - type: string - example: "Virtual image conversion failed" - "500 (Error)": - description: "Error al convertir la imagen virtual." - schema: - type: object - properties: - success: - type: boolean - example: false - error: - type: string - example: "(Error description)" - "500 (Exception)": - description: "Excepción inesperada al convertir la imagen virtual." - schema: - type: object - properties: - success: - type: boolean - example: false - exception: - type: string - example: "(Exception description)" - -# ----------------------------------------------------------------------------------------------------------- - - #/ogrepository/v1/images/virtual: - 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). - - **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" - 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) - schema: - type: object - properties: - ID_img: - type: string - example: "22735b9070e4a8043371b8c6ae52b90d" - vm_extension: - type: string - example: "vdi" - responses: - "200": - description: "La imagen se está convirtiendo a virtual." - schema: - type: object - properties: - success: - type: boolean - example: true - output: - type: string - example: "Converting image to virtual..." - "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 virtual exportada con el mismo nombre que la imagen." - schema: - type: object - properties: - success: - type: boolean - example: false - exception: - type: string - example: "There is an exported virtual image with the same name as the image" - "400 (No disk space)": - description: "No hay espacio suficiente en disco." - schema: - type: object - properties: - success: - type: boolean - example: false - exception: - type: string - example: "There is not enough free disk space" - "500": - description: "Error al convertir la imagen a virtual." - schema: - type: object - properties: - success: - type: boolean - example: false - error: - type: string - example: "Image conversion to virtual failed" - "500 (Error)": - description: "Error al convertir la imagen a virtual." - schema: - type: object - properties: - success: - type: boolean - example: false - error: - type: string - example: "(Error description)" - "500 (Exception)": - description: "Excepción inesperada al convertir la imagen a virtual." - schema: - type: object - properties: - success: - type: boolean - example: false - exception: - type: string - example: "(Exception description)" - -# ----------------------------------------------------------------------------------------------------------- -# Apartado "Varios" -# ----------------------------------------------------------------------------------------------------------- - - /ogrepository/v1/images/torrentsum: - post: - summary: "Crear archivos auxiliares" - description: | - Este endpoint crea los archivos "**size**", "**sum**", "**full.sum**" y "**torrent**" para la imagen especificada. - Utiliza el script "**createTorrentSum.py**", que recibe como parámetro el nombre de la imagen (con extensión), que a su vez llama al script "**updateRepoInfo.py**, para actualizar la información del repositorio". - - Debe ser llamado cada vez que se cree una imagen desde un ogLive, y cada vez que se llame al endpoint "**Exportar una Imagen**" (en este último caso, debe ejecutarse en el ogRepository destino de la exportación). - - **NOTA**: Este endpoint es asíncrono, ya que puede tardar cierto tiempo, por lo que solo informa de que se están creando los archivos auxiliares, y abre un proceso paralelo, que avisará a ogCore cuando finalice la tarea (llamando a un endpoint de ogCore). - tags: - - "Varios" - parameters: - - name: JSON - in: body - required: true - description: | - * **image** - Nombre de la imagen, con extensión - schema: - type: object - properties: - image: - type: string - example: "Windows10.img" - responses: - "200": - description: "Los archivos auxiliares se están creando." - schema: - type: object - properties: - success: - type: boolean - example: true - output: - type: string - example: "Creating auxiliar files..." - "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" - "500 (Error)": - description: "Error al crear los archivos auxiliares." - schema: - type: object - properties: - success: - type: boolean - example: false - error: - type: string - example: "(Error description)" - "500 (Exception)": - description: "Excepción inesperada al crear los archivos auxiliares." - schema: - type: object - properties: - success: - type: boolean - example: false - exception: - type: string - example: "(Exception description)" - -# ----------------------------------------------------------------------------------------------------------- - - /ogrepository/v1/wol: - post: - summary: "Enviar paquete Wake On Lan" - description: | - Este endpoint envía un paquete mágico Wake On Lan (**WOL**) a la dirección MAC especificada, a través de la IP de broadcast especificada. - Utiliza el script "**sendWakeOnLan.py**", que recibe como parámetros la IP de broadcast y la dirección MAC del equipo a encender. - tags: - - "Varios" - parameters: - - name: JSON - in: body - required: true - description: | - * **broadcast_ip** - IP de broadcast a la que se enviará el paquete WOL, que puede ser '255.255.255.255' o la IP de broadcast de una subred - * **mac** - Dirección MAC del equipo que se desea encender via Wake On Lan - schema: - type: object - properties: - broadcast_ip: - type: string - example: "255.255.255.255" - mac: - type: string - example: "00:19:99:5c:bb:bb" - responses: - "200": - description: "El paquete Wake On Lan se ha enviado exitosamente." - schema: - type: object - properties: - success: - type: boolean - example: true - output: - type: string - example: "Wake On Lan packet sent successfully" - "500 (Error)": - description: "Error al enviar el paquete Wake On Lan." - schema: - type: object - properties: - success: - type: boolean - example: false - error: - type: string - example: "(Error description)" - "500 (Exception)": - description: "Excepción inesperada al enviar el paquete Wake On Lan." - schema: - type: object - properties: - success: - type: boolean - example: false - exception: - type: string - example: "(Exception description)" - -# ----------------------------------------------------------------------------------------------------------- diff --git a/api_server/debian/changelog b/api_server/debian/changelog deleted file mode 100644 index b45ba24..0000000 --- a/api_server/debian/changelog +++ /dev/null @@ -1,5 +0,0 @@ -oggit (0.5) UNRELEASED; urgency=medium - - * Initial release. - - -- OpenGnsys Fri, 14 Mar 2025 08:40:35 +0100 diff --git a/api_server/debian/control b/api_server/debian/control deleted file mode 100644 index f19f87d..0000000 --- a/api_server/debian/control +++ /dev/null @@ -1,38 +0,0 @@ -Source: oggit -Section: unknown -Priority: optional -Maintainer: OpenGnsys -Rules-Requires-Root: no -Build-Depends: - debhelper-compat (= 13), -Standards-Version: 4.6.2 -Homepage: https://opengnsys.es -#Vcs-Browser: https://salsa.debian.org/debian/ogboot -#Vcs-Git: https://salsa.debian.org/debian/ogboot.git - -Package: oggit -Architecture: any -Multi-Arch: foreign -Depends: - ${shlibs:Depends}, - ${misc:Depends}, - bsdextrautils, - debconf (>= 1.5.0), - gunicorn, - opengnsys-flask-executor, - opengnsys-flask-restx, - opengnsys-libarchive-c, - python3, - python3-aniso8601, - python3-flasgger, - python3-flask, - python3-flask, - python3-git, - python3-paramiko, - python3-requests, - python3-termcolor, - python3-tqdm, - opengnsys-forgejo (>= 0.5) -Conflicts: -Description: Opengnsys Oggit package - Files for OpenGnsys Git support diff --git a/api_server/debian/copyright b/api_server/debian/copyright deleted file mode 100644 index 5d5fd7e..0000000 --- a/api_server/debian/copyright +++ /dev/null @@ -1,43 +0,0 @@ -Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ -Source: -Upstream-Name: ogboot -Upstream-Contact: - -Files: - * -Copyright: - - -License: GPL-3.0+ - -Files: - debian/* -Copyright: - 2025 vagrant -License: GPL-3.0+ - -License: GPL-3.0+ - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - . - This package is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - . - You should have received a copy of the GNU General Public License - along with this program. If not, see . -Comment: - On Debian systems, the complete text of the GNU General - Public License version 3 can be found in "/usr/share/common-licenses/GPL-3". - -# Please also look if there are files or directories which have a -# different copyright/license attached and list them here. -# Please avoid picking licenses with terms that are more restrictive than the -# packaged work, as it may make Debian's contributions unacceptable upstream. -# -# If you need, there are some extra license texts available in two places: -# /usr/share/debhelper/dh_make/licenses/ -# /usr/share/common-licenses/ diff --git a/api_server/debian/oggit-docs.docs b/api_server/debian/oggit-docs.docs deleted file mode 100644 index 71672b8..0000000 --- a/api_server/debian/oggit-docs.docs +++ /dev/null @@ -1,3 +0,0 @@ -README.source -README.Debian -README diff --git a/api_server/debian/oggit.dirs b/api_server/debian/oggit.dirs deleted file mode 100644 index 5af69b6..0000000 --- a/api_server/debian/oggit.dirs +++ /dev/null @@ -1,3 +0,0 @@ -/opt/opengnsys/images/git -/opt/opengnsys/ogrepository/oggit -/opt/opengnsys/ogrepository/oggit/api \ No newline at end of file diff --git a/api_server/debian/oggit.install b/api_server/debian/oggit.install deleted file mode 100644 index a8624d4..0000000 --- a/api_server/debian/oggit.install +++ /dev/null @@ -1,6 +0,0 @@ -api_server.py /opt/opengnsys/ogrepository/oggit/api -../installer/opengnsys_git_installer.py /opt/opengnsys/oggit/bin -blueprints/gitapi.py /opt/opengnsys/ogrepository/oggit/api/blueprints -blueprints/repo_api.py /opt/opengnsys/ogrepository/oggit/api/blueprints -opengnsys-repository-api.service /etc/systemd/system - diff --git a/api_server/debian/oggit.postinst b/api_server/debian/oggit.postinst deleted file mode 100644 index 5d7faed..0000000 --- a/api_server/debian/oggit.postinst +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/bash -/usr/bin/systemctl daemon-reload -/usr/bin/systemctl enable --now opengnsys-repository-api diff --git a/api_server/debian/oggit.substvars b/api_server/debian/oggit.substvars deleted file mode 100644 index 978fc8b..0000000 --- a/api_server/debian/oggit.substvars +++ /dev/null @@ -1,2 +0,0 @@ -misc:Depends= -misc:Pre-Depends= diff --git a/api_server/debian/oggit/DEBIAN/control b/api_server/debian/oggit/DEBIAN/control deleted file mode 100644 index 83cdad0..0000000 --- a/api_server/debian/oggit/DEBIAN/control +++ /dev/null @@ -1,12 +0,0 @@ -Package: oggit -Version: 0.5 -Architecture: amd64 -Maintainer: OpenGnsys -Installed-Size: 193 -Depends: bsdextrautils, debconf (>= 1.5.0), gunicorn, opengnsys-flask-executor, opengnsys-flask-restx, opengnsys-libarchive-c, python3, python3-aniso8601, python3-flasgger, python3-flask, python3-git, python3-paramiko, python3-requests, python3-termcolor, python3-tqdm -Section: unknown -Priority: optional -Multi-Arch: foreign -Homepage: https://opengnsys.es -Description: Opengnsys Oggit package - Files for OpenGnsys Git support diff --git a/api_server/debian/oggit/DEBIAN/md5sums b/api_server/debian/oggit/DEBIAN/md5sums deleted file mode 100644 index 0bf7046..0000000 --- a/api_server/debian/oggit/DEBIAN/md5sums +++ /dev/null @@ -1,6 +0,0 @@ -1a0024adb1d5e54ecff27759c5ac4a7d opt/opengnsys/oggit/bin/api_server.py -bd0a968737c2d62ce44490414426ccbb opt/opengnsys/oggit/bin/opengnsys_git_installer.py -af5f26474949def90af8794458f3f08d opt/opengnsys/oggit/blueprints/gitapi.py -61618848e4caca8b22e3cc7b9c8706b8 opt/opengnsys/oggit/blueprints/repo_api.py -48b531f72dec218fcdd61dce26f6b5ab usr/share/doc/oggit/changelog.gz -8a13e4a3eb6149d56094319bbed84d0c usr/share/doc/oggit/copyright diff --git a/api_server/debian/oggit/opt/opengnsys/oggit/bin/api_server.py b/api_server/debian/oggit/opt/opengnsys/oggit/bin/api_server.py deleted file mode 100755 index ed5f315..0000000 --- a/api_server/debian/oggit/opt/opengnsys/oggit/bin/api_server.py +++ /dev/null @@ -1,139 +0,0 @@ -#!/usr/bin/env python3 -import os -import sys - -sys.path.insert(0, "/usr/share/opengnsys-modules/python3/dist-packages") - - -import importlib -import logging -import uuid -import argparse -from flask import Flask, request -from flask_executor import Executor -from flask_restx import Api -from werkzeug.exceptions import HTTPException -from systemd.journal import JournalHandler - - -parser = argparse.ArgumentParser( - prog="api_server.py", - description="OpenGnsys Repository API Server", -) - -debug_enabled = False -listen_host = '0.0.0.0' -parser.add_argument('--debug', action='store_true', help="Enable debug output") -parser.add_argument('--listen', metavar="HOST", help="Listen address") -parser.add_argument("-v", "--verbose", action="store_true", help = "Verbose console output") - -args = parser.parse_args() - - - -log = logging.getLogger('api_server') -log.addHandler(JournalHandler()) - -if args.verbose: - log.addHandler(logging.StreamHandler(stream=sys.stderr)) - log.setLevel(logging.DEBUG) -else: - log.setLevel(logging.INFO) - -if args.listen: - listen_host = args.listen - - -if args.debug: - debug_enabled = True - - -api_base_dir = os.path.dirname(os.path.realpath(__file__)) -blueprints_dir = os.path.join(api_base_dir, 'blueprints') -installer_dir = os.path.join(api_base_dir, '../installer') - - - - -sys.path.insert(0, installer_dir) - - - - - -# Create an instance of the Flask class -app = Flask(__name__) -api = Api(app, - version='0.50', - title = "OpenGnsys Git API", - description = "API for managing disk images stored in Git", - doc = "/swagger/") - - -executor = Executor(app) - -log.info("Loading blueprints from %s", blueprints_dir) -sys.path.insert(0, blueprints_dir) - -for filename in os.listdir(blueprints_dir): - if filename.endswith('.py'): - - log.info("Loading %s/%s", blueprints_dir, filename) - - module_name = filename.replace(".py", "") - - log.info("Importing %s", module_name) - importlib.invalidate_caches() - module = importlib.import_module(module_name) - log.debug("Returned: %s", module) - - app.register_blueprint(module.blueprint) - - -@app.errorhandler(HTTPException) -def handle_exception(e): - """Return JSON for HTTP errors. - - We create and log an error UUID for each error, and use journald's additional fields for easier searching. - """ - # start with the correct headers and status code from the error - response = e.get_response() - - errid = uuid.uuid4().hex - - - if debug_enabled: - response = { - "errcode": e.code, - "errname": e.name, - "description": e.description, - } - else: - response = { - "errcode" : 500, - "errname" : "Internal error", - "description": f"Please see the log for error {errid}", - "error_id" : errid - } - - log.error("Error ID %s: code %i, name %s, description %s", errid, e.code, e.name, e.description, extra = { "error_id" : errid, "errcode" : e.code, "errname" : e.name, "description" : e.description }) - - return response, 500 - -@app.after_request -def after_request(response): - log.info("Request from %s: %s %s %s %s", request.remote_addr, request.method, request.scheme, request.full_path, response.status, - extra = {"remote_addr" : request.remote_addr, "method" : request.method, "scheme" : request.scheme, "full_path" : request.full_path, "status" : response.status}) - - if debug_enabled: - log.debug("Response: %s", response.data, extra = {"response" : response.data}) - - return response - - - - -# Run the Flask app -if __name__ == '__main__': - print(f"Map: {app.url_map}") - app.run(debug=debug_enabled, host=listen_host) diff --git a/api_server/debian/oggit/opt/opengnsys/oggit/bin/opengnsys_git_installer.py b/api_server/debian/oggit/opt/opengnsys/oggit/bin/opengnsys_git_installer.py deleted file mode 100755 index 97c7e72..0000000 --- a/api_server/debian/oggit/opt/opengnsys/oggit/bin/opengnsys_git_installer.py +++ /dev/null @@ -1,1069 +0,0 @@ -#!/usr/bin/env python3 -"""Script para la instalación del repositorio git""" - -import os -import sys -sys.path.insert(0, "/usr/share/opengnsys-modules/python3/dist-packages") - - -import shutil -import argparse -import tempfile -import logging -import subprocess -import sys -import pwd -import grp -from termcolor import cprint -import git -import libarchive -from libarchive.extract import * - -#from libarchive.entry import FileType -import urllib.request -import pathlib -import socket -import time -import requests -import tempfile -import hashlib -import datetime -import tqdm - -#FORGEJO_VERSION="8.0.3" -FORGEJO_VERSION="10.0.1" -FORGEJO_URL=f"https://codeberg.org/forgejo/forgejo/releases/download/v{FORGEJO_VERSION}/forgejo-{FORGEJO_VERSION}-linux-amd64" - - - -def download_with_progress(url, output_file): - - with requests.get(url, stream=True, timeout=60) as req: - progress = tqdm.tqdm() - progress.total = int(req.headers["Content-Length"]) - progress.unit_scale = True - progress.desc = "Downloading" - - for chunk in req.iter_content(chunk_size=8192): - output_file.write(chunk) - progress.n = progress.n + len(chunk) - progress.refresh() - - progress.close() - -def show_error(*args): - """ - Imprime un mensaje de error - - Args: - *args: Argumentos igual que a la función print - - Returns: - None - """ - cprint(*args, "red", attrs = ["bold"], file=sys.stderr) - - -class RequirementException(Exception): - """Excepción que indica que nos falta algún requisito - - Attributes: - message (str): Mensaje de error mostrado al usuario - """ - - def __init__(self, message): - """Inicializar RequirementException. - - Args: - message (str): Mensaje de error mostrado al usuario - """ - super().__init__(message) - self.message = message - - -class OptionalDependencyException(Exception): - """Excepción que indica que nos falta algún requisito opcional - - Attributes: - message (str): Mensaje de error mostrado al usuario - """ - - def __init__(self, message): - """Inicializar OptionalDependencyException. - - Args: - message (str): Mensaje de error mostrado al usuario - """ - super().__init__(message) - self.message = message - -class FakeTemporaryDirectory: - """Imitación de TemporaryDirectory para depuración""" - def __init__(self, dirname): - self.name = dirname - os.makedirs(dirname, exist_ok=True) - - def __str__(self): - return self.name - - -class OgliveMounter: - """ - A class to handle mounting of Oglive images from a given URL or local file. - - Attributes: - logger (logging.Logger): Logger instance for logging messages. - squashfs (str): Path to the squashfs file within the mounted Oglive image. - initrd (str): Path to the initrd image within the mounted Oglive image. - kernel (str): Path to the kernel image within the mounted Oglive image. - - Methods: - __init__(url): - Initializes the OgliveMounter instance, downloads the Oglive image if URL is provided, - and mounts the image to a temporary directory. - - __del__(): - Unmounts the mounted directory and cleans up resources. - """ - def __init__(self, url): - self.logger = logging.getLogger("OgliveMounter") - self.mountdir = tempfile.TemporaryDirectory() - - self.logger.info("Will mount oglive found at %s", url) - - if url.startswith("http://") or url.startswith("https://"): - self.logger.debug("We got an URL, downloading %s", url) - self.tempfile = tempfile.NamedTemporaryFile(mode='wb') - filename = self.tempfile.name - - download_with_progress(url, self.tempfile) - else: - self.logger.debug("We got a filename") - filename = url - - self.logger.debug("Mounting %s at %s", filename, self.mountdir.name) - subprocess.run(["/usr/bin/mount", filename, self.mountdir.name], check=True) - - self.squashfs = os.path.join(self.mountdir.name, "ogclient", "ogclient.sqfs") - self.initrd = os.path.join(self.mountdir.name, "ogclient", "oginitrd.img") - self.kernel = os.path.join(self.mountdir.name, "ogclient", "ogvmlinuz") - - - def __del__(self): - self.logger.debug("Unmounting directory %s", self.mountdir.name) - subprocess.run(["/usr/bin/umount", self.mountdir.name], check=True) - - - - - -class Oglive: - """Interfaz a utilidad oglivecli - - Esto es probablemente temporal hasta que se haga una conversión de oglivecli - """ - - def __init__(self): - self.__logger = logging.getLogger("Oglive") - - self.binary = "/opt/opengnsys/bin/oglivecli" - self.__logger.debug("Inicializando") - - def _cmd(self, args): - cmd = [self.binary] + args - - if not os.path.exists(self.binary): - raise OptionalDependencyException("Missing oglivecli command. Please use --squashfs-file (see README.md for more details)") - - self.__logger.debug("comando: %s", cmd) - - proc = subprocess.run(cmd, shell=False, check=True, capture_output=True) - out_text = proc.stdout.decode('utf-8').strip() - self.__logger.debug("salida: %s", out_text) - return out_text - - def get_default(self): - """Devuelve el cliente por defecto""" - self.__logger.debug("get_default()") - return self._cmd(["get-default"]) - - def get_clients(self): - """Devuelve la lista de clientes en un dict""" - self.__logger.debug("get_clients()") - lines = self._cmd(["list"]).splitlines() - clients = {} - for line in lines: - (number, name) = line.split() - clients[number] = name - - self.__logger.debug("Clientes: %s", clients) - return clients - -class OpengnsysGitInstaller: - """Instalador de OpenGnsys""" - - def __init__(self): - """Inicializar clase""" - self.__logger = logging.getLogger("OpengnsysGitInstaller") - self.__logger.setLevel(logging.DEBUG) - self.__logger.debug("Inicializando") - self.testmode = False - self.base_path = "/opt/opengnsys" - self.ogrepository_base_path = os.path.join(self.base_path, "ogrepository") - self.git_basedir = "base.git" - self.email = "OpenGnsys@opengnsys.com" - - self.forgejo_user = "oggit" - self.forgejo_password = "opengnsys" - self.forgejo_organization = "opengnsys" - self.forgejo_port = 3000 - - self.dependencies = ["git", "python3-flask", "python3-flasgger", "gunicorn", ] - - self.set_ssh_user_group("oggit", "oggit") - - self.temp_dir = None - self.script_path = os.path.realpath(os.path.dirname(__file__)) - - # Possible names for SSH public keys - self.ssh_key_users = ["root", "opengnsys"] - self.key_names = ["id_rsa.pub", "id_ed25519.pub", "id_ecdsa.pub", "id_ed25519_sk.pub", "id_ecdsa_sk.pub"] - - # Possible names for SSH key in oglive - self.key_paths = ["scripts/ssl/id_rsa.pub", "scripts/ssl/id_ed25519.pub", "scripts/ssl/id_ecdsa.pub", "scripts/ssl/id_ed25519_sk.pub", "scripts/ssl/id_ecdsa_sk.pub"] - - self.key_paths_dict = {} - - for kp in self.key_paths: - self.key_paths_dict[kp] = 1 - - os.environ["PATH"] += os.pathsep + os.path.join(self.base_path, "bin") - - self.oglive = Oglive() - - - def set_testmode(self, value): - """Establece el modo de prueba""" - self.testmode = value - - def set_ignoresshkey(self, value): - """Ignorar requisito de clave de ssh para el instalador""" - self.ignoresshkey = value - - def set_basepath(self, value): - """Establece ruta base de OpenGnsys - Valor por defecto: /opt/opengnsys - """ - self.base_path = value - - def _get_tempdir(self): - """Obtiene el directorio temporal""" - if self.testmode: - dirname = "/tmp/ogtemp" - if os.path.exists(dirname): - shutil.rmtree(dirname) - - dir=FakeTemporaryDirectory(dirname) - self.__logger.debug("Modo de prueba, temp=/tmp/ogtemp") - return dir - else: - dir = tempfile.TemporaryDirectory() - self.__logger.debug("Temp = %s", dir) - return dir - - def _cleanup(self): - """Limpia el directorio temporal""" - if self.temp_dir: - shutil.rmtree(self.temp_dir, ignore_errors=True) - - def set_ssh_user_group(self, username, groupname): - - - self.ssh_group = groupname - self.ssh_user = username - - try: - self.ssh_gid = grp.getgrnam(self.ssh_group).gr_gid - self.__logger.info("Group %s exists with gid %i", self.ssh_group, self.ssh_gid) - except KeyError: - self.__logger.info("Need to create group %s", self.ssh_group) - subprocess.run(["/usr/sbin/groupadd", "--system", self.ssh_group], check=True) - self.ssh_gid = grp.getgrnam(groupname).gr_gid - - - try: - self.ssh_uid = pwd.getpwnam(self.ssh_user).pw_uid - self.__logger.info("User %s exists with gid %i", self.ssh_user, self.ssh_uid) - except KeyError: - self.__logger.info("Need to create user %s", self.ssh_user) - subprocess.run(["/usr/sbin/useradd", "--gid", str(self.ssh_gid), "-m", "--system", self.ssh_user], check=True) - self.ssh_uid = pwd.getpwnam(username).pw_uid - - self.ssh_homedir = pwd.getpwnam(username).pw_dir - - - def init_git_repo(self, reponame): - """Inicializa un repositorio Git""" - # Creamos repositorio - ogdir_images = os.path.join(self.ogrepository_base_path, "oggit") - self.__logger.info("Creando repositorio de GIT %s", reponame) - - os.makedirs(os.path.join(ogdir_images, self.git_basedir), exist_ok=True) - - repo_path=os.path.join(ogdir_images, reponame) - shutil.rmtree(repo_path, ignore_errors=True) - - - # Marcar como directorio seguro - # Nota: no usar GitPython. Config global falla, aunque hay indicaciones de que - # git.Repo(path=None) es valido. Posiblemente bug de GitPython. - - subprocess.run(["git", "config", "--global", "add" "safe.directory", repo_path]) - - self.__logger.debug("Inicializando repositorio: " + repo_path) - repo = git.Repo.init(repo_path, bare = True) - - self.__logger.info("Configurando repositorio de GIT") - repo.config_writer().set_value("user", "name", "OpenGnsys").release() - repo.config_writer().set_value("user", "email", self.email).release() - - self._recursive_chown(repo_path, ouid=self.ssh_uid, ogid=self.ssh_gid) - - def _add_line_to_file(self, filename, new_line): - """Agrega una línea a un archivo""" - found = False - - self.__logger.debug("Agregando linea: %s a %s", new_line, filename) - with open(filename, "a+", encoding="utf-8") as f: - f.seek(0) - - for line in f: - if line.strip() == new_line.strip(): - found = True - - if not found: - self.__logger.debug("Agregando linea: %s", new_line) - f.write(new_line + "\n") - else: - self.__logger.debug("Linea ya presente") - - def _recursive_chown(self, path, ouid, ogid): - """Cambia el propietario y grupo de forma recursiva""" - for dirpath, _, filenames in os.walk(path): - os.chown(dirpath, uid=ouid, gid=ogid) - for filename in filenames: - os.chown(os.path.join(dirpath, filename), uid=ouid, gid=ogid) - - def _wait_for_port(self, host, port): - self.__logger.info("Waiting for %s:%i to be up", host, port) - - timeout = 60 - start_time = time.time() - - ready = False - while not ready and (time.time() - start_time) < 60: - s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - try: - s.connect((host, port)) - ready = True - s.close() - except TimeoutError: - self.__logger.debug("Timed out, no connection yet.") - except OSError as oserr: - self.__logger.debug("%s, no connection yet. %.1f seconds left.", oserr.strerror, timeout - (time.time() - start_time)) - - time.sleep(0.1) - - if ready: - self.__logger.info("Connection established.") - else: - self.__logger.error("Timed out waiting for connection!") - raise TimeoutError("Timed out waiting for connection!") - - - def add_ssh_key_from_squashfs(self, oglive_num = None, squashfs_file = None, oglive_file = None): - - name = "(unknown)" - mounter = None - - if not oglive_file is None: - mounter = OgliveMounter(oglive_file) - squashfs_file = mounter.squashfs - - if squashfs_file is None: - if oglive_num is None: - self.__logger.info("Using default oglive") - oglive_num = self.oglive.get_default() - else: - self.__logger.info("Using oglive %i", oglive_num) - - name = self.oglive.get_clients()[str(oglive_num)] - - else: - self.__logger.info("Using specified squashfs file %s", squashfs_file) - name = os.path.basename(squashfs_file) - - - keys = installer.extract_ssh_keys_from_squashfs(oglive_num = oglive_num, squashfs_file=squashfs_file) - for k in keys: - timestamp = '{:%Y-%m-%d %H:%M:%S}'.format(datetime.datetime.now()) - installer.add_forgejo_sshkey(k, f"Key for {name} ({timestamp})") - - - - def extract_ssh_keys_from_squashfs(self, oglive_num = None, squashfs_file = None): - public_keys = [] - - - squashfs = "ogclient.sqfs" - - if squashfs_file is None: - tftp_dir = os.path.join(self.base_path, "tftpboot") - - if oglive_num is None: - self.__logger.info("Reading from default oglive") - oglive_num = self.oglive.get_default() - else: - self.__logger.info("Reading from oglive %i", oglive_num) - - oglive_client = self.oglive.get_clients()[str(oglive_num)] - self.__logger.info("Oglive is %s", oglive_client) - - client_squashfs_path = os.path.join(tftp_dir, oglive_client, squashfs) - else: - self.__logger.info("Using specified squashfs file %s", squashfs_file) - client_squashfs_path = squashfs_file - - self.__logger.info("Mounting %s", client_squashfs_path) - mount_tempdir = tempfile.TemporaryDirectory() - ssh_keys_dir = os.path.join(mount_tempdir.name, "root", ".ssh") - - subprocess.run(["mount", client_squashfs_path, mount_tempdir.name], check=True) - for file in os.listdir(ssh_keys_dir): - full_path = os.path.join(ssh_keys_dir, file) - - if file.endswith(".pub"): - self.__logger.info("Found public key: %s", full_path) - - with open(full_path, "r", encoding="utf-8") as keyfile: - keydata = keyfile.read().strip() - public_keys = public_keys + [keydata] - - - subprocess.run(["umount", mount_tempdir.name], check=True) - - return public_keys - - - def extract_ssh_key_from_initrd(self, oglive_number = None, initrd_file = None): - public_key="" - - INITRD = "oginitrd.img" - - self.__logger.debug("Extracting ssh key from initrd") - - if initrd_file is None: - self.__logger.debug("Looking for initrd file") - tftp_dir = os.path.join(self.base_path, "tftpboot") - if oglive_number is None: - oglive_number = self.oglive.get_default() - - oglive_client = self.oglive.get_clients()[oglive_number] - client_initrd_path = os.path.join(tftp_dir, oglive_client, INITRD) - - self.__logger.debug("Found at %s", client_initrd_path) - else: - self.__logger.debug("Using provided initrd file %s", initrd_file) - client_initrd_path = initrd_file - - self.__logger.debug("Extracting key from %s", client_initrd_path) - - if os.path.isfile(client_initrd_path): - #os.makedirs(temp_dir, exist_ok=True) - #os.chdir(self.temp_dir.name) - self.__logger.debug("Uncompressing %s", client_initrd_path) - public_key = None - with libarchive.file_reader(client_initrd_path) as initrd: - for file in initrd: - self.__logger.debug("File: %s", file) - - pathname = file.pathname; - if pathname.startswith("./"): - pathname = pathname[2:] - - if pathname in self.key_paths_dict: - self.__logger.info("Found key %s, extracting", pathname) - - data = bytearray() - for block in file.get_blocks(): - data = data + block - public_key = data.decode('utf-8').strip() - - break - else: - print(f"Failed to find initrd at {client_initrd_path}") - exit(2) - - if not public_key: - self.__logger.warning("Failed to find a SSH key") - - return public_key - - def get_image_paths(self, oglive_num = None): - squashfs = "ogclient.sqfs" - - if oglive_num is None: - self.__logger.info("Will modify default client") - oglive_num = self.oglive.get_default() - - tftp_dir = os.path.join(self.base_path, "tftpboot") - oglive_client = self.oglive.get_clients()[str(oglive_num)] - - client_squashfs_path = os.path.join(tftp_dir, oglive_client, squashfs) - - self.__logger.info("Squashfs: %s", client_squashfs_path) - - - def set_ssh_key_in_initrd(self, client_num = None): - INITRD = "oginitrd.img" - - tftp_dir = os.path.join(self.base_path, "tftpboot") - - if client_num is None: - self.__logger.info("Will modify default client") - client_num = self.oglive.get_default() - - - ogclient = self.oglive.get_clients()[client_num] - client_initrd_path = os.path.join(tftp_dir, ogclient, INITRD) - client_initrd_path_new = client_initrd_path + ".new" - - self.__logger.debug("initrd path for ogclient %s is %s", ogclient, client_initrd_path) - - - - - - temp_dir = tempfile.TemporaryDirectory() - temp_dir_path = temp_dir.name - - #temp_dir_path = "/tmp/extracted" - if os.path.exists(temp_dir_path): - shutil.rmtree(temp_dir_path) - - - pathlib.Path(temp_dir_path).mkdir(parents=True, exist_ok = True) - - self.__logger.debug("Uncompressing initrd %s into %s", client_initrd_path, temp_dir_path) - os.chdir(temp_dir_path) - libarchive.extract_file(client_initrd_path, flags = EXTRACT_UNLINK | EXTRACT_OWNER | EXTRACT_PERM | EXTRACT_FFLAGS | EXTRACT_TIME) - ssh_key_dir = os.path.join(temp_dir_path, "scripts", "ssl") - - client_key_path = os.path.join(ssh_key_dir, "id_ed25519") - authorized_keys_path = os.path.join(ssh_key_dir, "authorized_keys") - - oglive_public_key = "" - - - # Create a SSH key on the oglive, if needed - pathlib.Path(ssh_key_dir).mkdir(parents=True, exist_ok=True) - if os.path.exists(client_key_path): - self.__logger.info("Creating SSH key not necessary, it already is in the initrd") - else: - self.__logger.info("Writing new SSH key into %s", client_key_path) - subprocess.run(["/usr/bin/ssh-keygen", "-t", "ed25519", "-N", "", "-f", client_key_path], check=True) - - with open(client_key_path + ".pub", "r", encoding="utf-8") as pubkey: - oglive_public_key = pubkey.read() - - # Add our public keys to the oglive, so that we can log in - public_keys = "" - - for username in self.ssh_key_users: - self.__logger.debug("Looking for keys in user %s", username) - homedir = pwd.getpwnam(username).pw_dir - - for key in self.key_names: - key_path = os.path.join(homedir, ".ssh", key) - self.__logger.debug("Checking if we have %s...", key_path) - if os.path.exists(key_path): - with open(key_path, "r", encoding='utf-8') as public_key_file: - self.__logger.info("Adding %s to authorized_keys", key_path) - public_key = public_key_file.read() - public_keys = public_keys + public_key + "\n" - - self.__logger.debug("Writing %s", authorized_keys_path) - with open(authorized_keys_path, "w", encoding='utf-8') as auth_keys: - auth_keys.write(public_keys) - - - - # hardlinks in the source package are not correctly packaged back as hardlinks. - # Taking the easy option of turning them into symlinks for now. - file_hashes = {} - with libarchive.file_writer(client_initrd_path_new, "cpio_newc", "zstd") as writer: - - file_list = [] - for root, subdirs, files in os.walk(temp_dir_path): - proot = pathlib.PurePosixPath(root) - relpath = proot.relative_to(temp_dir_path) - - for file in files: - abs_path = os.path.join(root, file) - full_path = os.path.join(relpath, file) - - #self.__logger.debug("%s", abs_path) - digest = None - - if os.path.islink(abs_path): - self.__logger.debug("%s is a symlink", abs_path) - continue - - if not os.path.exists(abs_path): - self.__logger.debug("%s does not exist", abs_path) - continue - - stat_data = os.stat(abs_path) - with open(full_path, "rb") as in_file: - digest = hashlib.file_digest(in_file, "sha256").hexdigest() - - if stat_data.st_size > 0 and not os.path.islink(full_path): - if digest in file_hashes: - target_path = pathlib.Path(file_hashes[digest]) - link_path = target_path.relative_to(relpath, walk_up=True) - - self.__logger.debug("%s was a duplicate of %s, linking to %s", full_path, file_hashes[digest], link_path) - - os.unlink(full_path) - #os.link(file_hashes[digest], full_path) - os.symlink(link_path, full_path) - else: - file_hashes[digest] = full_path - - - writer.add_files(".", recursive=True ) - - os.rename(client_initrd_path, client_initrd_path + ".old") - - if os.path.exists(client_initrd_path + ".sum"): - os.rename(client_initrd_path + ".sum", client_initrd_path + ".sum.old") - - os.rename(client_initrd_path_new, client_initrd_path) - - - with open(client_initrd_path, "rb") as initrd_file: - hexdigest = hashlib.file_digest(initrd_file, "sha256").hexdigest() - with open(client_initrd_path + ".sum", "w", encoding="utf-8") as digest_file: - digest_file.write(hexdigest + "\n") - - self.__logger.info("Updated initrd %s", client_initrd_path) - - - timestamp = '{:%Y-%m-%d %H:%M:%S}'.format(datetime.datetime.now()) - - self.add_forgejo_sshkey(oglive_public_key, f"Key for {ogclient} ({timestamp})") - - - def verify_requirements(self): - self.__logger.info("verify_requirements()") - - # Control básico de errores. - self.__logger.debug("Comprobando euid") - if os.geteuid() != 0: - raise RequirementException("Sólo ejecutable por root") - - if not os.path.exists("/etc/debian_version"): - raise RequirementException("Instalación sólo soportada en Debian y Ubuntu") - - - MIN_PYTHON = (3, 8) - if sys.version_info < MIN_PYTHON: - raise RequirementException(f"Python %s.%s mínimo requerido.\n" % MIN_PYTHON) - - - def install_dependencies(self): - """Instalar - - Ejecuta todo el proceso de instalación incluyendo: - * Dependencias - * Configuración de authorized_keys - * Configuración de ssh - * Creación de repositorio - - Raises: - RequirementException: No ejecutado por usuario root - RequirementException: No ejecutado en Debian o Ubuntu - RequirementException: Falta clave pública - RequirementException: Python < 3.8 - """ - self.__logger.info("install()") - - - self.verify_requirements() - - self.__logger.debug("Installing dependencies") - subprocess.run(["apt-get", "install", "-y"] + self.dependencies, check=True) - - def _install_template(self, template, destination, keysvalues): - - self.__logger.info("Writing template %s into %s", template, destination) - - data = "" - with open(template, "r", encoding="utf-8") as template_file: - data = template_file.read() - - for key in keysvalues.keys(): - if isinstance(keysvalues[key], int): - data = data.replace("{" + key + "}", str(keysvalues[key])) - else: - data = data.replace("{" + key + "}", keysvalues[key]) - - with open(destination, "w+", encoding="utf-8") as out_file: - out_file.write(data) - - def _runcmd(self, cmd): - self.__logger.debug("Running: %s", cmd) - - ret = subprocess.run(cmd, check=True,capture_output=True, encoding='utf-8') - return ret.stdout.strip() - - def install_api(self): - self.__logger.info("Installing Git API") - - opengnsys_bin_path = os.path.join(self.base_path, "bin") - opengnsys_etc_path = os.path.join(self.base_path, "etc") - - pathlib.Path(opengnsys_bin_path).mkdir(parents=True, exist_ok=True) - - - data = { - "gitapi_user" : "opengnsys", - "gitapi_group" : "opengnsys", - "gitapi_host" : "0.0.0.0", - "gitapi_port" : 8087, - "gitapi_work_path" : opengnsys_bin_path - } - - shutil.copy("../api/gitapi.py", opengnsys_bin_path + "/gitapi.py") - shutil.copy("opengnsys_git_installer.py", opengnsys_bin_path + "/opengnsys_git_installer.py") - - self._install_template(os.path.join(self.script_path, "gitapi.service"), "/etc/systemd/system/gitapi.service", data) - - - - self.__logger.debug("Reloading systemd and starting service") - subprocess.run(["systemctl", "daemon-reload"], check=True) - subprocess.run(["systemctl", "enable", "gitapi"], check=True) - subprocess.run(["systemctl", "restart", "gitapi"], check=True) - - - def install_forgejo(self): - self.__logger.info("Installing Forgejo version %s", FORGEJO_VERSION) - - - - - opengnsys_bin_path = os.path.join(self.base_path, "bin") - opengnsys_etc_path = os.path.join(self.base_path, "etc") - - forgejo_bin_path = os.path.join(self.ogrepository_base_path, "bin") - bin_path = os.path.join(forgejo_bin_path, "forgejo") - conf_dir_path = os.path.join(self.ogrepository_base_path, "etc", "forgejo") - - - lfs_dir_path = os.path.join(self.ogrepository_base_path, "oggit", "git-lfs") - git_dir_path = os.path.join(self.ogrepository_base_path, "oggit", "git") - - forgejo_work_dir_path = os.path.join(self.ogrepository_base_path, "var", "lib", "forgejo/work") - forgejo_db_dir_path = os.path.join(self.ogrepository_base_path, "var", "lib", "forgejo/db") - forgejo_data_dir_path = os.path.join(self.ogrepository_base_path, "var", "lib", "forgejo/data") - - forgejo_db_path = os.path.join(forgejo_db_dir_path, "forgejo.db") - - forgejo_log_dir_path = os.path.join(self.ogrepository_base_path, "log", "forgejo") - - - conf_path = os.path.join(conf_dir_path, "app.ini") - - self.__logger.info("Stopping opengnsys-forgejo service. This may cause a harmless warning.") - - subprocess.run(["/usr/bin/systemctl", "stop", "opengnsys-forgejo"], check=False) - - self.__logger.debug("Downloading from %s into %s", FORGEJO_URL, bin_path) - pathlib.Path(forgejo_bin_path).mkdir(parents=True, exist_ok=True) - - with open(bin_path, "wb") as forgejo_bin: - download_with_progress(FORGEJO_URL, forgejo_bin) - - os.chmod(bin_path, 0o755) - - if os.path.exists(forgejo_db_path): - self.__logger.debug("Removing old configuration") - os.unlink(forgejo_db_path) - else: - self.__logger.debug("Old configuration not present, ok.") - - self.__logger.debug("Wiping old data") - for dir in [conf_dir_path, git_dir_path, lfs_dir_path, forgejo_work_dir_path, forgejo_data_dir_path, forgejo_db_dir_path]: - if os.path.exists(dir): - self.__logger.debug("Removing %s", dir) - shutil.rmtree(dir) - - self.__logger.debug("Creating directories") - - pathlib.Path(opengnsys_etc_path).mkdir(parents=True, exist_ok=True) - pathlib.Path(conf_dir_path).mkdir(parents=True, exist_ok=True) - pathlib.Path(git_dir_path).mkdir(parents=True, exist_ok=True) - pathlib.Path(lfs_dir_path).mkdir(parents=True, exist_ok=True) - pathlib.Path(forgejo_work_dir_path).mkdir(parents=True, exist_ok=True) - pathlib.Path(forgejo_data_dir_path).mkdir(parents=True, exist_ok=True) - pathlib.Path(forgejo_db_dir_path).mkdir(parents=True, exist_ok=True) - pathlib.Path(forgejo_log_dir_path).mkdir(parents=True, exist_ok=True) - - - os.chown(lfs_dir_path, self.ssh_uid, self.ssh_gid) - os.chown(git_dir_path, self.ssh_uid, self.ssh_gid) - os.chown(forgejo_data_dir_path, self.ssh_uid, self.ssh_gid) - os.chown(forgejo_work_dir_path, self.ssh_uid, self.ssh_gid) - os.chown(forgejo_db_dir_path, self.ssh_uid, self.ssh_gid) - os.chown(forgejo_log_dir_path, self.ssh_uid, self.ssh_gid) - - data = { - "forgejo_user" : self.ssh_user, - "forgejo_group" : self.ssh_group, - "forgejo_port" : str(self.forgejo_port), - "forgejo_bin" : bin_path, - "forgejo_app_ini" : conf_path, - "forgejo_work_path" : forgejo_work_dir_path, - "forgejo_data_path" : forgejo_data_dir_path, - "forgejo_db_path" : forgejo_db_path, - "forgejo_repository_root" : git_dir_path, - "forgejo_lfs_path" : lfs_dir_path, - "forgejo_log_path" : forgejo_log_dir_path, - "forgejo_hostname" : self._runcmd("hostname"), - "forgejo_lfs_jwt_secret" : self._runcmd([bin_path,"generate", "secret", "LFS_JWT_SECRET"]), - "forgejo_jwt_secret" : self._runcmd([bin_path,"generate", "secret", "JWT_SECRET"]), - "forgejo_internal_token" : self._runcmd([bin_path,"generate", "secret", "INTERNAL_TOKEN"]), - "forgejo_secret_key" : self._runcmd([bin_path,"generate", "secret", "SECRET_KEY"]) - } - - self._install_template(os.path.join(self.script_path, "forgejo-app.ini"), conf_path, data) - self._install_template(os.path.join(self.script_path, "forgejo.service"), "/etc/systemd/system/opengnsys-forgejo.service", data) - - - self.__logger.debug("Reloading systemd and starting service") - subprocess.run(["systemctl", "daemon-reload"], check=True) - subprocess.run(["systemctl", "enable", "opengnsys-forgejo"], check=True) - subprocess.run(["systemctl", "restart", "opengnsys-forgejo"], check=True) - - self.__logger.info("Waiting for forgejo to start") - self._wait_for_port("localhost", self.forgejo_port) - - - self.__logger.info("Configuring forgejo") - - def run_forge_cmd(args): - cmd = [bin_path, "--config", conf_path] + args - self.__logger.debug("Running command: %s", cmd) - - ret = subprocess.run(cmd, check=False, capture_output=True, encoding='utf-8', user=self.ssh_user) - if ret.returncode == 0: - return ret.stdout.strip() - else: - self.__logger.error("Failed to run command: %s, return code %i", cmd, ret.returncode) - self.__logger.error("stdout: %s", ret.stdout) - self.__logger.error("stderr: %s", ret.stderr) - raise RuntimeError("Failed to run necessary command") - - run_forge_cmd(["admin", "doctor", "check"]) - - run_forge_cmd(["admin", "user", "create", "--username", self.forgejo_user, "--password", self.forgejo_password, "--email", self.email]) - - token = run_forge_cmd(["admin", "user", "generate-access-token", "--username", self.forgejo_user, "-t", "gitapi", "--scopes", "all", "--raw"]) - - with open(os.path.join(self.base_path, "etc", "ogGitApiToken.cfg"), "w+", encoding='utf-8') as token_file: - token_file.write(token) - - - def add_forgejo_repo(self, repository_name, description = ""): - token = "" - with open(os.path.join(self.base_path, "etc", "ogGitApiToken.cfg"), "r", encoding='utf-8') as token_file: - token = token_file.read().strip() - - self.__logger.info("Adding repository %s for Forgejo", repository_name) - - r = requests.post( - f"http://localhost:{self.forgejo_port}/api/v1/user/repos", - json={ - "auto_init" : False, - "default_branch" : "main", - "description" : description, - "name" : repository_name, - "private" : False - }, headers={ - 'Authorization' : f"token {token}" - }, - timeout = 60 - ) - - self.__logger.info("Request status was %i, content %s", r.status_code, r.content) - - def add_forgejo_sshkey(self, pubkey, description = ""): - token = "" - with open(os.path.join(self.base_path, "etc", "ogGitApiToken.cfg"), "r", encoding='utf-8') as token_file: - token = token_file.read().strip() - - self.__logger.info("Adding SSH key to Forgejo: %s (%s)", pubkey, description) - - r = requests.post( - f"http://localhost:{self.forgejo_port}/api/v1/user/keys", - json={ - "key" : pubkey, - "read_only" : False, - "title" : description - }, headers={ - 'Authorization' : f"token {token}" - }, - timeout = 60 - ) - - self.__logger.info("Request status was %i, content %s", r.status_code, r.content) - - def add_forgejo_organization(self, pubkey, description = ""): - token = "" - with open(os.path.join(self.base_path, "etc", "ogGitApiToken.cfg"), "r", encoding='utf-8') as token_file: - token = token_file.read().strip() - - self.__logger.info("Adding SSH key to Forgejo: %s", pubkey) - - r = requests.post( - f"http://localhost:{self.forgejo_port}/api/v1/user/keys", - json={ - "key" : pubkey, - "read_only" : False, - "title" : description - }, headers={ - 'Authorization' : f"token {token}" - }, - timeout = 60 - ) - - self.__logger.info("Request status was %i, content %s", r.status_code, r.content) - - - -if __name__ == '__main__': - sys.stdout.reconfigure(encoding='utf-8') - - opengnsys_log_dir = "/opt/opengnsys/log" - - logger = logging.getLogger(__package__) - logger.setLevel(logging.DEBUG) - - streamLog = logging.StreamHandler() - streamLog.setLevel(logging.INFO) - - pathlib.Path(opengnsys_log_dir).mkdir(parents=True, exist_ok=True) - - logFilePath = f"{opengnsys_log_dir}/git_installer.log" - fileLog = logging.FileHandler(logFilePath) - fileLog.setLevel(logging.DEBUG) - - formatter = logging.Formatter('%(asctime)s - %(name)24s - [%(levelname)5s] - %(message)s') - - streamLog.setFormatter(formatter) - fileLog.setFormatter(formatter) - - logger.addHandler(streamLog) - logger.addHandler(fileLog) - - - parser = argparse.ArgumentParser( - prog="OpenGnsys Installer", - description="Script para la instalación del repositorio git", - ) - parser.add_argument('--forgejo-only', action='store_true', help="Solo instalar forgejo") - parser.add_argument('--forgejo-addrepos', action='store_true', help="Solo agregar repositorios forgejo") - - parser.add_argument('--testmode', action='store_true', help="Modo de prueba") - parser.add_argument('--ignoresshkey', action='store_true', help="Ignorar clave de SSH") - parser.add_argument('--use-ssh-key', metavar="FILE", type=str, help="Add the SSH key from the specified file") - parser.add_argument('--test-createuser', action='store_true') - parser.add_argument('--extract-ssh-key', action='store_true', help="Extract SSH key from oglive squashfs") - parser.add_argument('--set-ssh-key', action='store_true', help="Read SSH key from oglive squashfs and set it in Forgejo") - - parser.add_argument('--extract-ssh-key-from-initrd', action='store_true', help="Extract SSH key from oglive initrd (obsolete)") - - parser.add_argument('--initrd-file', metavar="FILE", help="Initrd file to extract SSH key from") - parser.add_argument('--squashfs-file', metavar="FILE", help="Squashfs file to extract SSH key from") - parser.add_argument('--oglive-file', metavar="FILE", help="Oglive file (ISO) to extract SSH key from") - parser.add_argument('--oglive-url', metavar="URL", help="URL to oglive file (ISO) to extract SSH key from") - - - parser.add_argument('--set-ssh-key-in-initrd', action='store_true', help="Configure SSH key in oglive (obsolete)") - parser.add_argument('--oglive', type=int, metavar='NUM', help = "Do SSH key manipulation on this oglive") - parser.add_argument('--quiet', action='store_true', help="Quiet console output") - parser.add_argument('--get-image-paths', action='store_true', help="Get paths to image files") - parser.add_argument("-v", "--verbose", action="store_true", help = "Verbose console output") - - - - args = parser.parse_args() - - if args.quiet: - streamLog.setLevel(logging.WARNING) - - if args.verbose: - streamLog.setLevel(logging.DEBUG) - - installer = OpengnsysGitInstaller() - installer.set_testmode(args.testmode) - installer.set_ignoresshkey(args.ignoresshkey) - - logger.debug("Inicio de instalación") - - try: - if args.forgejo_only: - installer.install_forgejo() - elif args.forgejo_addrepos: - installer.add_forgejo_repo("linux") - elif args.test_createuser: - installer.set_ssh_user_group("oggit2", "oggit2") - elif args.extract_ssh_key: - keys = installer.extract_ssh_keys_from_squashfs(oglive_num = args.oglive) - print(f"{keys}") - elif args.extract_ssh_key_from_initrd: - key = installer.extract_ssh_key_from_initrd(oglive_number = args.oglive, initrd_file = args.initrd_file) - print(f"{key}") - elif args.set_ssh_key: - installer.add_ssh_key_from_squashfs(oglive_num=args.oglive, squashfs_file=args.squashfs_file, oglive_file = args.oglive_file or args.oglive_url) - elif args.use_ssh_key: - with open(args.use_ssh_key, 'r', encoding='utf-8') as ssh_key_file: - ssh_key_data = ssh_key_file.read().strip() - (keytype, keydata, description) = ssh_key_data.split(" ", 2) - - installer.add_forgejo_sshkey(f"{keytype} {keydata}", description) - - elif args.set_ssh_key_in_initrd: - installer.set_ssh_key_in_initrd() - elif args.get_image_paths: - installer.get_image_paths(oglive_num = args.oglive) - else: - installer.install_dependencies() - installer.install_api() - installer.install_forgejo() - - installer.add_forgejo_repo("windows", "Windows") - installer.add_forgejo_repo("linux", "Linux") - installer.add_forgejo_repo("mac", "Mac") - - installer.add_ssh_key_from_squashfs(oglive_num = args.oglive, squashfs_file=args.squashfs_file, oglive_file = args.oglive_file or args.oglive_url) - - except RequirementException as req: - show_error(f"Requisito para la instalación no satisfecho: {req.message}") - exit(1) - except OptionalDependencyException as optreq: - show_error(optreq.message) - exit(1) - - diff --git a/api_server/debian/oggit/opt/opengnsys/oggit/blueprints/gitapi.py b/api_server/debian/oggit/opt/opengnsys/oggit/blueprints/gitapi.py deleted file mode 100755 index 3383a53..0000000 --- a/api_server/debian/oggit/opt/opengnsys/oggit/blueprints/gitapi.py +++ /dev/null @@ -1,573 +0,0 @@ -#!/usr/bin/env python3 -""" -This module provides a Flask-based API for managing Git repositories in the OpenGnsys system. -It includes endpoints for creating, deleting, synchronizing, backing up, and performing garbage -collection on Git repositories. The API also provides endpoints for retrieving repository -information such as the list of repositories and branches, as well as checking the status of -asynchronous tasks. - -Classes: - None - -Functions: - do_repo_backup(repo, params) - - do_repo_sync(repo, params) - - do_repo_gc(repo) - - home() - - get_repositories() - - create_repo(repo) - - sync_repo(repo) - - backup_repository(repo) - - gc_repo(repo) - - tasks_status(task_id) - - delete_repo(repo) - - get_repository_branches(repo) - - health_check() - -Constants: - REPOSITORIES_BASE_PATH (str): The base path where Git repositories are stored. - -Global Variables: - app (Flask): The Flask application instance. - executor (Executor): The Flask-Executor instance for managing asynchronous tasks. - tasks (dict): A dictionary to store the status of asynchronous tasks. -""" - -# pylint: disable=locally-disabled, line-too-long - -import os.path -import os -import shutil -import uuid -import time -import logging -import traceback - -import git -from opengnsys_git_installer import OpengnsysGitInstaller -from flask import Blueprint, request -from flask_restx import Resource, Api -import paramiko -from systemd.journal import JournalHandler - - -debug_enabled = False - -log = logging.getLogger('gitapi') -log.addHandler(JournalHandler()) -log.setLevel(logging.INFO) -log.info("Started") - - -REPOSITORIES_BASE_PATH = "/opt/opengnsys/ogrepository/oggit/git/oggit/" - -start_time = time.time() -tasks = {} -tasks_max = 1024 - -blueprint = Blueprint('git_api', __name__, template_folder='templates', url_prefix = '/oggit/v1') -api = Api(blueprint) -git_ns = api - - -def add_task(future): - task_id = uuid.uuid4().hex - task_data = { - "future" : future, - "start_time" : time.time() - } - - while len(tasks) >= tasks_max: - oldest_task_id = min(tasks, key=lambda k: tasks[k]['start_time']) - task = tasks[task_id]["future"] - if task.running(): - log.error("Cancelling still running task %s, maximum task limit of %i reached", task_id, tasks_max) - task.cancel() - - del tasks[oldest_task_id] - - tasks[task_id] = task_data - return task_id - -def do_repo_backup(repo, params): - """ - Creates a backup of the specified Git repository and uploads it to a remote server via SFTP. - - Args: - repo (str): The name of the repository to back up. - params (dict): A dictionary containing the following keys: - - ssh_server (str): The SSH server address. - - ssh_port (int): The SSH server port. - - ssh_user (str): The SSH username. - - filename (str): The remote filename where the backup will be stored. - - Returns: - bool: True if the backup was successful. - """ - - git_repo_path = f"{REPOSITORIES_BASE_PATH}/{repo}.git" - git_repo = git.Repo(git_repo_path) - git_repo.git.config('--global', '--add', 'safe.directory', git_repo_path) - - - ssh = paramiko.SSHClient() - ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) - - ssh.connect(params["ssh_server"], params["ssh_port"], params["ssh_user"]) - sftp = ssh.open_sftp() - - - with sftp.file(params["filename"], mode='wb+') as remote_file: - git_repo.archive(remote_file, format="tar.gz") - - - return True - -def do_repo_sync(repo, params): - """ - Synchronizes a local Git repository with a remote repository. - - Args: - repo (str): The name of the local repository to synchronize. - params (dict): A dictionary containing the remote repository URL with the key "remote_repository". - - Returns: - list: A list of dictionaries, each containing: - - "local_ref" (str): The name of the local reference. - - "remote_ref" (str): The name of the remote reference. - - "summary" (str): A summary of the push operation for the reference. - """ - git_repo_path = f"{REPOSITORIES_BASE_PATH}/{repo}.git" - git_repo = git.Repo(git_repo_path) - git_repo.git.config('--global', '--add', 'safe.directory', git_repo_path) - - - # Recreate the remote every time, it might change - if "backup" in git_repo.remotes: - git_repo.delete_remote("backup") - - backup_repo = git_repo.create_remote("backup", params["remote_repository"]) - pushed_references = backup_repo.push("*:*") - results = [] - - # This gets returned to the API - for ref in pushed_references: - results = results + [ {"local_ref" : ref.local_ref.name, "remote_ref" : ref.remote_ref.name, "summary" : ref.summary }] - - return results - -def do_repo_gc(repo): - """ - Perform garbage collection on the specified Git repository. - - Args: - repo (str): The name of the repository to perform garbage collection on. - - Returns: - bool: True if the garbage collection command was executed successfully. - """ - git_repo_path = os.path.join(REPOSITORIES_BASE_PATH, repo + ".git") - git_repo = git.Repo(git_repo_path) - git_repo.git.config('--global', '--add', 'safe.directory', git_repo_path) - - git_repo.git.gc() - - - -# Define a route for the root URL -@api.route('/') -class GitLib(Resource): - - #@api.doc('home') - def get(self): - """ - Home route that returns a JSON response with a welcome message for the OpenGnsys Git API. - - Returns: - Response: A Flask JSON response containing a welcome message. - """ - log.info("Root URL accessed") - - return { - "message": "OpenGnsys Git API" - } - -@git_ns.route('/repositories') -class GitRepositories(Resource): - def get(self): - """ - 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"] - } - """ - - if not os.path.isdir(REPOSITORIES_BASE_PATH): - log.error("Can't list repositories. Repository storage at %s not found", REPOSITORIES_BASE_PATH, extra = {"path" : REPOSITORIES_BASE_PATH}) - return {"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] - - log.info("Returning %i repositories", len(repos)) - return { - "repositories": repos - } - - def post(self): - """ - 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: - log.error("Can't create repository, JSON post data missing") - return {"error" : "Parameters missing"}, 400 - - repo = data["name"] - - repo_path = os.path.join(REPOSITORIES_BASE_PATH, repo + ".git") - if os.path.isdir(repo_path): - log.error("Can't create repository %s, already exists at %s", repo, repo_path, extra = {"repository" : repo, "path" : repo_path}) - return {"status": "Repository already exists"}, 200 - - - installer = OpengnsysGitInstaller() - installer.add_forgejo_repo(repo) - - #installer.init_git_repo(repo + ".git") - - log.info("Repository %s created", repo, extra = {"repository" : repo}) - return {"status": "Repository created"}, 201 - - -@git_ns.route('/repositories//sync') -class GitRepoSync(Resource): - def post(self, repo): - """ - Synchronize a repository with a remote repository. - - This endpoint triggers the synchronization process for a specified repository. - It expects a JSON payload with the remote repository details. - - Args: - repo (str): The name of the repository to be synchronized. - - Returns: - Response: A JSON response indicating the status of the synchronization process. - - 200: If the synchronization process has started successfully. - - 400: If the request payload is missing or invalid. - - 404: If the specified repository is not found. - """ - repo_path = os.path.join(REPOSITORIES_BASE_PATH, repo + ".git") - if not os.path.isdir(repo_path): - log.error("Can't sync repository %s, not found. Looked in %s", repo, repo_path, extra = {"repository" : repo, "path" : repo_path }) - return {"error": "Repository not found"}, 404 - - - data = request.json - - if data is None: - log.error("Can't create repository, JSON post data missing") - return {"error" : "Parameters missing"}, 400 - - if not "remote_repository" in data: - log.error("Can't create repository, parameter 'remote_repository' missing") - return {"error" : "Parameter 'remote_repository' missing"}, 400 - - - future = executor.submit(do_repo_sync, repo, data) - task_id = add_task(future) - - log.info("Starting synchronization of repository %s, task %s", repo, task_id, extra = {"repository" : repo, "task_id" : task_id}) - return {"status": "started", "task_id" : task_id}, 200 - - - -@git_ns.route('/repositories//backup') -class GitRepoBackup(Resource): - def backup_repository(self, repo): - """ - Backup a specified repository. - - Endpoint: POST /repositories//backup - - Args: - repo (str): The name of the repository to back up. - - Request Body (JSON): - ssh_port (int, optional): The SSH port to use for the backup. Defaults to 22. - - Returns: - Response: A JSON response indicating the status of the backup operation. - - If the repository is not found, returns a 404 error with a message. - - If the request body is missing, returns a 400 error with a message. - - If the backup process starts successfully, returns a 200 status with the task ID. - - Notes: - - The repository path is constructed by appending ".git" to the repository name. - - The backup operation is performed asynchronously using a thread pool executor. - - The task ID of the backup operation is generated using UUID and stored in a global tasks dictionary. - """ - repo_path = os.path.join(REPOSITORIES_BASE_PATH, repo + ".git") - if not os.path.isdir(repo_path): - log.error("Can't backup repository %s, not found. Looked in %s", repo, repo_path, extra = {"repository" : repo, "path" : repo_path }) - return {"error": "Repository not found"}, 404 - - - data = request.json - if data is None: - log.error("Can't create repository, JSON post data missing") - return {"error" : "Parameters missing"}, 400 - - - if not "ssh_port" in data: - data["ssh_port"] = 22 - - - future = executor.submit(do_repo_backup, repo, data) - task_id = add_task(future) - - log.info("Starting backup of repository %s, task %s", repo, task_id, extra = {"repository" : repo, "task_id" : task_id}) - return {"status": "started", "task_id" : task_id}, 200 - -@git_ns.route('/repositories//compact', methods=['POST']) -class GitRepoCompact(Resource): - def post(self, repo): - """ - Initiates a garbage collection (GC) process for a specified Git repository. - - This endpoint triggers an asynchronous GC task for the given repository. - The task is submitted to an executor, and a unique task ID is generated - and returned to the client. - - Args: - repo (str): The name of the repository to perform GC on. - - Returns: - Response: A JSON response containing the status of the request and - a unique task ID if the repository is found, or an error - message if the repository is not found. - """ - repo_path = os.path.join(REPOSITORIES_BASE_PATH, repo + ".git") - if not os.path.isdir(repo_path): - log.error("Can't compact repository %s, not found. Looked in %s", repo, repo_path, extra = {"repository" : repo, "path" : repo_path }) - return {"error": "Repository not found"}, 404 - - future = executor.submit(do_repo_gc, repo) - task_id = add_task(future) - - log.info("Starting compaction of repository %s, task %s", repo, task_id, extra = {"repository" : repo, "task_id" : task_id}) - return {"status": "started", "task_id" : task_id}, 200 - - -@git_ns.route('/tasks//status') -class GitTaskStatus(Resource): - def get(self, task_id): - """ - Endpoint to check the status of a specific task. - - Args: - task_id (str): The unique identifier of the task. - - Returns: - Response: A JSON response containing the status of the task. - - If the task is not found, returns a 404 error with an error message. - - If the task is completed, returns a 200 status with the result. - - If the task is still in progress, returns a 202 status indicating the task is in progress. - """ - if not task_id in tasks: - log.error("Task %s was not found", task_id, extra = {"task_id" : task_id}) - return {"error": "Task not found"}, 404 - - future = tasks[task_id]["future"] - - try: - if future.done(): - result = future.result() - log.info("Returning completion of task %s", task_id, extra = {"task_id" : task_id}) - return {"status" : "completed", "result" : result}, 200 - else: - log.info("Task %s is still in progress", task_id, extra = {"task_id" : task_id}) - return {"status" : "in progress"}, 202 - except Exception as e: - errid = uuid.uuid4().hex - - - log.error("Task %s failed with exception %s, UUID %s", task_id, traceback.format_exception(e), errid, extra = {"task_id" : task_id, "exception" : traceback.format_exception(e), "error_id" : errid}) - return {"status" : "internal error", "error_id" : errid }, 500 - - -@git_ns.route('/repositories/', methods=['DELETE']) -class GitRepo(Resource): - def delete(self, repo): - """ - Deletes a Git repository. - - This endpoint deletes a Git repository specified by the `repo` parameter. - If the repository does not exist, it returns a 404 error with a message - indicating that the repository was not found. If the repository is successfully - deleted, it returns a 200 status with a message indicating that the repository - was deleted. - - Args: - repo (str): The name of the repository to delete. - - Returns: - Response: A JSON response with a status message and the appropriate HTTP status code. - """ - repo_path = os.path.join(REPOSITORIES_BASE_PATH, repo + ".git") - if not os.path.isdir(repo_path): - log.error("Can't delete repository %s, not found. Looked in %s", repo, repo_path, extra = {"repository" : repo, "path" : repo_path }) - return {"error": "Repository not found"}, 404 - - - shutil.rmtree(repo_path) - log.info("Deleted repository %s", repo, extra = {"repository" : repo}) - return {"status": "Repository deleted"}, 200 - - - - -@git_ns.route('/repositories//branches') -class GitRepoBranches(Resource): - def get(self, 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): - log.error("Can't get branches of repository repository %s, not found. Looked in %s", repo, repo_path, extra = {"repository" : repo, "path" : repo_path }) - return {"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] - - log.info("Returning %i branches", len(branches)) - return { - "branches": branches - } - -@git_ns.route('/repositories//branches/') -class GitRepoBranchesDeleter(Resource): - def delete(self, 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): - log.error("Can't get branches of repository repository %s, not found. Looked in %s", repo, repo_path, extra = {"repository" : repo, "path" : repo_path }) - 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: - log.error("Can't delete branch %s, not found in repository %s", branch, repo, extra = {"repository" : repo, "branch" : branch}) - return {"error": "Branch not found"}, 404 - - git_repo.delete_head(branch) - log.info("Branch %s of repository %s deleted", branch, repo, extra = {"repository" : repo, "branch" : branch}) - return {"status": "deleted"}, 200 - - -@git_ns.route('/health') -class GitHealth(Resource): - def get(self): - """ - Health check endpoint. - - This endpoint returns a JSON response indicating the health status of the application. - - Returns: - Response: A JSON response with a status key set to "OK". Currently it always returns - a successful value, but this endpoint can still be used to check that the API is - active and functional. - - """ - log.info("Health check endpoint called") - return { - "status": "OK" - } - -@git_ns.route('/status') -class GitStatus(Resource): - def get(self): - """ - Status check endpoint. - - This endpoint returns a JSON response indicating the status of the application. - - Returns: - Response: A JSON response with status information - - """ - log.info("Status endpoint called") - - return { - "uptime" : time.time() - start_time, - "active_tasks" : len(tasks) - } diff --git a/api_server/debian/oggit/opt/opengnsys/oggit/blueprints/repo_api.py b/api_server/debian/oggit/opt/opengnsys/oggit/blueprints/repo_api.py deleted file mode 100755 index d238947..0000000 --- a/api_server/debian/oggit/opt/opengnsys/oggit/blueprints/repo_api.py +++ /dev/null @@ -1,1791 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -""" - 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). - -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") -""" - -# -------------------------------------------------------------------------------------------- -# IMPORTS -# -------------------------------------------------------------------------------------------- - -from flask import Flask, jsonify, request, Blueprint -import os -import subprocess -import json -from time import sleep -import paramiko -import logging -import threading -import requests -import random -import hashlib -from systemd import journal -# Imports para Swagger: -from flasgger import Swagger -import yaml - - -# -------------------------------------------------------------------------------------------- -# VARIABLES -# -------------------------------------------------------------------------------------------- - -repo_path = '/opt/opengnsys/ogrepository/images/' # No borrar la barra final -script_path = '/opt/opengnsys/ogrepository/bin' -repo_file = '/opt/opengnsys/ogrepository/etc/repoinfo.json' -trash_file = '/opt/opengnsys/ogrepository/etc/trashinfo.json' -config_file = '/opt/opengnsys/ogrepository/etc/ogAdmRepo.cfg' - - -# -------------------------------------------------------------------------------------------- -# FUNCTIONS -# -------------------------------------------------------------------------------------------- - - -blueprint = Blueprint('repo_api', __name__, template_folder='templates') -app = blueprint - -# Creamos una instancia de la aplicación Flask: - -# Cargamos el contenido del archivo "swagger.yaml": -swagger_template = [] -#with open("swagger.yaml", "r") as file: -# swagger_template = yaml.safe_load(file) - -# Así cambiamos el nombre de la página (por defecto, es 'Flasgger'): -#swagger_config = Swagger.DEFAULT_CONFIG -#swagger_config['title'] = 'ogRepository API' - -#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: - if line.startswith('IPcore'): - IPcore = line.split('=')[1].strip() - journal.send(f"ogCore IP obtained ({IPcore})", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - journal.send(f"{{'component':'ogRepo', 'severity':'INFO', 'operation':'Run function get_IPcore', 'desc':'ogCore IP obtained ({IPcore})'}}", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api") - return IPcore - if IPcore is None: - journal.send("Can't obtain ogCore IP", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - journal.send("{'component':'ogRepo', 'severity':'ERROR', 'operation':'Run function get_IPcore', 'desc':'Unable to obtain ogCore IP'}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api") - return "IP no encontrada en el archivo de configuración" - - -# --------------------------------------------------------- - - -def get_image_params(image_id, search='all'): - """ A partir de un ID de imagen (que corresponde al "fullsum"), busca la imagen en el repositorio y/o en la papelera (dependiendo del parámetro "search"). - Si encuentra la imagen devuelve su nombre y su extensión en un diccionario, y si no encuentra la imagen especificada retorna "None". - El parámtro "search" tiene el valor predeterminado "all" (que hará que busque tanto en el repo como en la papelera), - pero se le puede pasar el valor "repo" (para que busque solo en el repo) o "trash" (para que busque solo en la papelera). - """ - journal.send("Running function 'get_image_params'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - - # Creamos un diccionario vacío, para almacenar los resultados: - result = {} - - # Abrimos y almacenamos el archivo "repoinfo.json" (solo si se ha de buscar en el repo, y si el archivo tiene contenido): - if (search == 'all' or search == 'repo') and os.path.getsize(repo_file) > 0: - with open(repo_file, 'r') as file: - repo_data = json.load(file) - # Iteramos la clave "images" y buscamos la imagen (y si la encontramos almacenamos el nombre y la extension): - for image in repo_data.get('images', []): - if image.get('fullsum') == image_id: - result['name'] = image.get('name') - result['extension'] = image.get('type') - journal.send("Image found in repository JSON file", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - journal.send("{'component':'ogRepo', 'severity':'INFO', 'operation':'Run function get_image_params', 'desc':'Image found in repository JSON file'}", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api") - return result - - # Abrimos y almacenamos el archivo "trashinfo.json" (solo si se ha de buscar en la papelera, y si el archivo tiene contenido): - if (search == 'all' or search == 'trash') and os.path.getsize(trash_file) > 0: - with open(trash_file, 'r') as file: - trash_data = json.load(file) - # Iteramos la clave "images" y buscamos la imagen (y si la encontramos almacenamos el nombre y la extension): - for image in trash_data.get('images', []): - if image.get('fullsum') == image_id: - result['name'] = image.get('name') - result['extension'] = image.get('type') - journal.send("Image found in trash JSON file", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - journal.send("{'component':'ogRepo', 'severity':'INFO', 'operation':'Run function get_image_params', 'desc':'Image found in trash JSON file'}", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api") - return result - - # Si no encontramos la imagen, retornamos "None": - journal.send("Image not found in JSON file", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - journal.send("{'component':'ogRepo', 'severity':'WARNING', 'operation':'Run function get_image_params', 'desc':'Image not found in JSON file'}", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api") - return None - - -# --------------------------------------------------------- - - -def search_process(process, string_to_search): - """ Busca procesos que contengan el valor del parámetro "process" y el valor del parámetro "string_to_search" (la ruta de la imagen, normalmente). - Si encuentra alguno retorna "True", y si no encuentra ninguno retorna "False". - """ - journal.send("Running function 'search_process'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - - try: - # Obtenemos todos los procesos que están corriendo, y almacenamos la salida y los errores: - result = subprocess.Popen(['ps', '-aux'], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding='UTF8') - out, error = result.communicate() - - # Almacenamos en una lista los procesos que contengan el proceso del parámetro y la cadena a buscar: - process_list = [line for line in out.split('\n') if process in line and string_to_search in line] - - # Si hemos encontrado algún proceso que cumpla las condiciones, retornamos "True", y si no retornamos "False": - if process_list != []: - journal.send("Process found", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - journal.send("{'component':'ogRepo', 'severity':'INFO', 'operation':'Run function search_process', 'desc':'Process found'}", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api") - return True - else: - journal.send("Process not found", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - journal.send("{'component':'ogRepo', 'severity':'WARNING', 'operation':'Run function search_process', 'desc':'Process not found'}", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api") - return False - # Si se ha producido una excepción, imprimimos el error: - except Exception as error_description: - journal.send(f"Function 'search_process' result KO (Exception: {str(error_description)})", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - journal.send(f"{{'component':'ogRepo', 'severity':'ERROR', 'operation':'Run function search_process', 'desc':'Result KO (Exception: {str(error_description)})'}}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api") - print(f"Unexpected error: {error_description}") - - -# --------------------------------------------------------- - - -def check_remote_connection(remote_ip, remote_user): - """ Comprueba la conexión SSH/SFTP con el servidor remoto que recibe como primer parámetro. - Se utiliza para chequear la conexión antes de importar o exportar una imagen. - """ - journal.send("Running function 'check_remote_connection'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - - try: - # 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()) - - # Conectamos con el equipo remoto por SSH (con claves): - ssh_client.connect(hostname=remote_ip, port=22, username=remote_user, passphrase='') - - # Iniciamos un cliente SFTP: - sftp_client = ssh_client.open_sftp() - - # Cerramos el cliente SSH y el cliente SFTP: - ssh_client.close() - sftp_client.close() - - # Retornamos "True", porque hemos conseguido conectar: - journal.send("Connection OK", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - journal.send("{'component':'ogRepo', 'severity':'INFO', 'operation':'Run function check_remote_connection', 'desc':'Connection OK'}", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api") - return True - - # Si se produce una excepción, retornamos "False": - except Exception as error_description: - journal.send(f"Function 'check_remote_connection' result KO (Exception: {str(error_description)})", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - journal.send(f"{{'component':'ogRepo', 'severity':'ERROR', 'operation':'Run function check_remote_connection', 'desc':'Result KO (Exception: {str(error_description)})'}}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api") - return False - - -# --------------------------------------------------------- - - -def check_remote_image(remote_ip, remote_user, image_file_path): - """ Conecta con el servidor remoto que recibe como primer parámetro, - para comprobar si la imagen que recibe como tercer parámetro existe, o si está bloqueada. - Se utiliza para chequear la imagen antes de importarla. - """ - journal.send("Running function 'check_remote_image'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - - # 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()) - - # Conectamos con el equipo remoto por SSH (con claves): - ssh_client.connect(hostname=remote_ip, port=22, username=remote_user, passphrase='') - - # Iniciamos un cliente SFTP: - sftp_client = ssh_client.open_sftp() - - # Si la imagen no existe, retornamos el mensaje correspondiente: - try: - sftp_client.stat(image_file_path) - except IOError: - return "Remote image not found" - - # Si la imagen existe pero está bloqueada, retornamos el mensaje correspondiente: - try: - sftp_client.stat(f"{image_file_path}.lock") - return "Remote image is locked" - except IOError: - print("Remote image is not locked, as expected") - - # Cerramos el cliente SSH y el cliente SFTP: - ssh_client.close() - sftp_client.close() - - -# --------------------------------------------------------- - - -def check_lock_local(image_file_path, job_id): - """ Cada minuto comprueba si existe un archivo ".lock" asociado a la imagen que recibe como parámetro, y si existe la imagen y sus archivos asociados. - Cuando ya no exista el archivo ".lock" (pero si los demás archivos), le comunicará a ogCore que la importación ha sido exitosa, y saldrá de bucle. - Cuando ya no exista el archivo ".lock" (pero tampoco los demás), le comunicará a ogCore que la importación ha fallado, y saldrá de bucle. - Mientras no se cumpla ninguna de las condiciones anteriores, y aun exista el archivo ".lock", seguirá haciendo la comprobación (repitiendo el bucle cada minuto). - """ - journal.send("Running function 'check_lock_local'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - - # Esperamos 30 segundos, para dar tiempo a que se cree el archivo ".lock": - sleep(30) - - # Creamos un bucle infinito: - while True: - # Si ya no existe el archivo ".lock" (pero sí existen los demás), respondemos a ogCore (con "success: True") y salimos del bucle: - if not os.path.exists(f"{image_file_path}.lock") and os.path.exists(image_file_path) and os.path.exists(f"{image_file_path}.full.sum") and os.path.exists(f"{image_file_path}.info.checked") and os.path.exists(f"{image_file_path}.size") and os.path.exists(f"{image_file_path}.sum") and os.path.exists(f"{image_file_path}.torrent"): - journal.send("Task finalized (image unlocked)", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - journal.send("{'component':'ogRepo', 'severity':'INFO', 'operation':'Run function check_lock_local', 'desc':'Image unlocked'}", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api") - - # Almacenamos en un diccionario los datos a enviar a ogCore: - data = { - 'job_id': job_id, - 'success': True - } - # Llamamos al endpoint de ogCore, enviando los datos del diccionario: - journal.send(f"Calling function 'recall_ogcore' (JOB_ID: {job_id}, SUCCESS: True)", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - recall_ogcore(data) - break - # Si no existe el archivo ".lock" (pero tampoco los demás), respondemos a ogCore (con "success: False") y salimos del bucle: - elif not os.path.exists(f"{image_file_path}.lock") and not os.path.exists(image_file_path) and not os.path.exists(f"{image_file_path}.full.sum") and not os.path.exists(f"{image_file_path}.info") and not os.path.exists(f"{image_file_path}.size") and not os.path.exists(f"{image_file_path}.sum") and not os.path.exists(f"{image_file_path}.torrent"): - journal.send("Imported image didn't pass the Integrity Check", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - journal.send("{'component':'ogRepo', 'severity':'ERROR', 'operation':'Run function check_lock_local', 'desc':'Integrity check failed'}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api") - - # Almacenamos en un diccionario los datos a enviar a ogCore: - data = { - 'job_id': job_id, - 'success': False - } - # Llamamos al endpoint de ogCore, enviando los datos del diccionario: - journal.send(f"Calling function 'recall_ogcore' (JOB_ID: {job_id}, SUCCESS: False)", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - recall_ogcore(data) - break - # Si aun existe el archivo ".lock" (y no se han cumplido las condiciones anteriores), imprimimos un mensaje en la API: - elif os.path.exists(f"{image_file_path}.lock"): - journal.send("Task in process (.lock file exists)", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - - # Esperamos 1 minuto para volver a realizar la comprobación: - sleep(60) - - -# --------------------------------------------------------- - - -def check_remote_backup(image_name, remote_ip, remote_user, remote_path, job_id): - """ Cada minuto comprueba si se ha copiado la imagen que recibe como primer parámetro (y sus archivos asociados) al equipo remoto que recibe como segundo parámetro, - y mientras eso no suceda seguirá haciendo la comprobación (en un bucle "while" que se ejecuta cada minuto). - Una vez copiados todos los archivos, chequea la integridad de la imagen, comparando el contenido de los archivos ".sum" y ".size" con los valores reales (que vuelve a calcular). - Si la comprobación es correcta saldrá del bucle, y si es incorrecta saldrá del bucle y borrará los archivos que se hayan copiado al equipo remoto - (y en ambos casos le comunicará a ogCore el resultado, llamando a la función "recall_ogcore"). - """ - journal.send("Running function 'check_remote_backup'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - - # 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()) - # Conectamos con el equipo remoto por SSH (con claves): - ssh_client.connect(hostname=remote_ip, port=22, username=remote_user, passphrase='') - # Iniciamos un cliente SFTP: - sftp_client = ssh_client.open_sftp() - - # Esperamos 30 segundos antes de empezar a realizar la comprobación: - sleep(30) - - # Creamos una lista con las extensiones de los archivos asociados a la imagen (incluyendo ninguna extensión, que corresponde a la propia imagen): - extensions = ['', '.size', '.sum', '.full.sum', '.info'] - - # 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: - for ext in extensions: - sftp_client.stat(f"{remote_path}{image_name}{ext}") - all_files_copied = True - except IOError: - all_files_copied = False - - # Si se han copiado todos los archivos, comprobamos la integridad de la imagen: - if all_files_copied == True: - journal.send("All files copied", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - - try: - # Calculamos el tamaño del la imagen exportada ("size") - file_size = sftp_client.stat(f"{remote_path}{image_name}").st_size - - # Calculamos el hash MD5 del último MB de la imagen exportada ("sum"): - last_mb_offset = max(0, file_size - 1024 * 1024) - with sftp_client.file(f"{remote_path}{image_name}", 'rb') as file: - file.seek(last_mb_offset) - last_mb = file.read(1024 * 1024) - file_sum = hashlib.md5(last_mb).hexdigest() - - # Obtenemos el valor almacenado en el archivo ".size: - with sftp_client.file(f"{remote_path}{image_name}.size", 'r') as file: - stored_size = int(file.read().strip()) - - # Obtenemos el valor almacenado en el archivo ".sum: - with sftp_client.file(f"{remote_path}{image_name}.sum", 'r') as file: - stored_sum = file.read().strip().decode('utf-8') - - except Exception as error: - journal.send(f"Integrity check returned an error: {error}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - journal.send("{'component':'ogRepo', 'severity':'ERROR', 'operation':'Run function check_remote_backup', 'desc':'Integrity check returned an error'}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api") - - # Borramos los archivos copiados, porque no hemos podido chequear la integridad de la imagen: - remove_remote_files(sftp_client, remote_path, image_name, extensions) - - # Almacenamos en un diccionario los datos a enviar a ogCore: - data = { - 'job_id': job_id, - 'success': False - } - # Llamamos al endpoint de ogCore, enviando los datos del diccionario: - journal.send(f"Calling function 'recall_ogcore' (JOB_ID: {job_id}, SUCCESS: False)", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - recall_ogcore(data) - break - - # Comprobamos si los datos almacenados coinciden con los datos obtenidos (en cuyo caso el backup habrá sido correcto): - if file_sum == stored_sum and file_size == stored_size: - journal.send("Task finalized (backup complete)", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - journal.send("{'component':'ogRepo', 'severity':'INFO', 'operation':'Run function check_remote_backup', 'desc':'Backup complete'}", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api") - - # Almacenamos en un diccionario los datos a enviar a ogCore: - data = { - 'job_id': job_id, - 'success': True - } - # Llamamos al endpoint de ogCore, enviando los datos del diccionario: - journal.send(f"Calling function 'recall_ogcore' (JOB_ID: {job_id}, SUCCESS: True)", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - recall_ogcore(data) - break - else: - journal.send("Exported image didn't pass the Integrity Check", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - journal.send("{'component':'ogRepo', 'severity':'ERROR', 'operation':'Run function check_remote_backup', 'desc':'Integrity check failed'}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api") - - # Borramos los archivos copiados, porque el chequeo de la integridad de la imagen no ha sido exitoso: - remove_remote_files(sftp_client, remote_path, image_name, extensions) - - # Almacenamos en un diccionario los datos a enviar a ogCore: - data = { - 'job_id': job_id, - 'success': False - } - # Llamamos al endpoint de ogCore, enviando los datos del diccionario: - journal.send(f"Calling function 'recall_ogcore' (JOB_ID: {job_id}, SUCCESS: False)", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - recall_ogcore(data) - break - else: - journal.send("Task in process (backup incomplete)", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - - # Esperamos 1 minuto para volver a realizar la comprobación: - sleep(60) - - # Ya fuera del bucle, cerramos el cliente SSH y el cliente SFTP: - ssh_client.close() - sftp_client.close() - - -# --------------------------------------------------------- - - -def check_aux_files(image_file_path, job_id): - """ Cada 10 segundos comprueba si se han creado todos los archivos auxiliares de la imagen que recibe como parámetro, - en cuyo caso lo comunicará a ogCore, llamando a un endpoint, y dejará de realizar la comprobación. - También obtiene el valor del archivo ".full.sum" (que corresonde al ID), y se lo comunica a ogCore. - """ - journal.send("Running function 'check_aux_files'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - - # Creamos un bucle infinito: - while True: - # Si faltan archivos auxiliares por crear, imprimimos un mensaje en la API: - if not os.path.exists(f"{image_file_path}.size") or not os.path.exists(f"{image_file_path}.sum") or not os.path.exists(f"{image_file_path}.full.sum") or not os.path.exists(f"{image_file_path}.torrent") or not os.path.exists(f"{image_file_path}.info.checked"): - journal.send("Task in process (auxiliar files remaining)", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - # Si ya se han creado todos los archivos auxiliares, imprimimos un mensaje en la API, respondemos a ogCore y salimos del bucle: - else: - journal.send("Task finalized (all auxilar files created)", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - journal.send("{'component':'ogRepo', 'severity':'INFO', 'operation':'Run function check_aux_files', 'desc':'Auxiliar files created'}", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api") - - # Obtenemos el valor del archivo "full.sum", que corresponde al ID: - with open(f"{image_file_path}.full.sum", 'r') as file: - image_id = file.read().strip('\n') - - # Almacenamos en un diccionario los datos a enviar a ogCore: - data = { - 'job_id': job_id, - 'image_id': image_id - } - # Llamamos al endpoint de ogCore, enviando los datos del diccionario: - journal.send(f"Calling function 'recall_ogcore' (JOB_ID: {job_id}, IMAGE_ID: {image_id})", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - recall_ogcore(data) - break - # Esperamos 10 segundos para volver a realizar la comprobación: - sleep(10) - - -# --------------------------------------------------------- - - -def recall_ogcore(data): - """ Hace una petición HTTP de tipo POST a un endpoint de ogCore, enviando datos que dependen del caso. - Se utiliza para informar a ogCore del resultado de una tarea asíncrona, - que estaba corriendo en un proceso independiente (no controlado por los endpoints). - """ - journal.send("Running function 'recall_ogcore'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - - # Almacenamos la IP de ogCore: - ogcore_ip = get_IPcore() - - # Almacenamos la URL del endpoint de ogCore: - endpoint_url = f"https://{ogcore_ip}:8443/og-repository/webhook" - - # Almacenamos los headers, y convertiomos "data" a JSON: - headers = {'content-type': 'application/json'} - data = json.dumps(data) - - # Hacemos una petición POST al endpoint, enviando lo almacenado en "data": - journal.send("Sending HTTP request...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - response = requests.post(endpoint_url, data=data, headers=headers, verify=False) - - # Imprimimos el código de estado de la petición y la respuesta de ogCore: - journal.send(f"HTTP Status Code: {response.status_code}", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - #journal.send(f"HTTP Response: {response.text}", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - journal.send(f"{{'component':'ogRepo', 'severity':'INFO', 'operation':'Run function recall_ogcore', 'desc':'HTTP Status Code response: {response.status_code}'}}", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api") - - -# --------------------------------------------------------- - - -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". - """ - journal.send("Running function 'check_file_exists'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - - # Comprobamos si existe el archivo de la ruta "file_path": - if os.path.exists(file_path): - return True - else: - return False - - -# --------------------------------------------------------- - - -def remove_remote_files(sftp_client, remote_path, image_name, extensions): - """ Borra la imagen "image_name" y sus archivos asociados (cuyas extensiones están almacenadas en la lista "extensions") - del equipo remoto al que nos hemos conectado previamente (mediante el objeto "sftp_client", que recibe como primer parámetro). - Es llamada por la función "check_remote_backup", cuando la imagen exportada no pasa el test de integridad. - """ - journal.send("Running function 'remove_remote_files'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - - # Iteramos las extensiones de la lista "extensions", para eliminar la imagen "image_name" y sus archivos asociados: - for ext in extensions: - try: - sftp_client.stat(f"{remote_path}{image_name}{ext}") - sftp_client.remove(f"{remote_path}{image_name}{ext}") - journal.send(f"File with extension {ext} removed", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - except IOError: - journal.send(f"File with extension {ext} doesn't exist", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - - - -# -------------------------------------------------------------------------------------------- -# ENDPOINTS -# -------------------------------------------------------------------------------------------- - - -# 1 - Endpoint "Obtener Información de Estado de ogRepository" (SINCRONO): -@app.route("/ogrepository/v1/status", methods=['GET']) -def get_repo_status(): - """ Este endpoint devuelve información de CPU, memoria RAM, disco duro y el estado de ciertos servicios y procesos de ogRepository, en formato json. - Para ello, ejecuta el script "getRepoStatus.py", que no recibe parámetros. - """ - journal.send("Running endpoint 'Obtener Información de Estado de ogRepository'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - - try: - 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") - journal.send("{'component':'ogRepo', 'severity':'INFO', 'http_code':'200', 'operation':'Run script getRepoStatus.py', 'desc':'Result OK (ReturnCode: 0)'}", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api") - return jsonify({ - "success": True, - "output": json.loads(result.stdout) - }), 200 - else: - journal.send(f"Script 'getRepoStatus.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 getRepoStatus.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 'getRepoStatus.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 getRepoStatus.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 - - -# --------------------------------------------------------- - - -# 2 - Endpoint "Obtener Información de todas las Imágenes" (SINCRONO): -@app.route("/ogrepository/v1/images", methods=['GET']) -def get_repo_info(): - """ Este endpoint devuelve información de todas las imágenes contenidas en el repositorio (incluída la papelera), en formato json. - Para ello, ejecuta el script "getRepoInfo.py", con el parámetro "all". - """ - journal.send("Running endpoint 'Obtener Información de todas las Imágenes'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - - try: - journal.send("Running script 'getRepoInfo.py'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - # Ejecutamos el script "getRepoInfo.py" (con el parámetro "all"), y almacenamos el resultado: - result = subprocess.run(['python3', f"{script_path}/getRepoInfo.py", 'all'], 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 'getRepoInfo.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 getRepoInfo.py', 'desc':'Result OK (ReturnCode: 0)'}", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api") - return jsonify({ - "success": True, - "output": json.loads(result.stdout) - }), 200 - else: - journal.send(f"Script 'getRepoInfo.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 getRepoInfo.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 'getRepoInfo.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 getRepoInfo.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 - - -# --------------------------------------------------------- - - -# 3 - Endpoint "Obtener Información de una Imagen concreta" (SINCRONO): -@app.route("/ogrepository/v1/images/", methods=['GET']) -def get_repo_image_info(imageId): - """ Este endpoint devuelve información de la imagen especificada como parámetro, en formato json. - Para ello, ejecuta el script "getRepoInfo.py", con el nombre de la imagen como parámetro. - """ - journal.send("Running endpoint 'Obtener Información de una Imagen concreta'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - - # Obtenemos el nombre y la extensión de la imagen: - param_dict = get_image_params(imageId) - - # Evaluamos los parámetros obtenidos, para construir la llamada al script, o para devover un error si no se ha encontrado la imagen: - if param_dict: - cmd = ['python3', f"{script_path}/getRepoInfo.py", f"{param_dict['name']}.{param_dict['extension']}"] - 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 get_repo_image_info', 'desc': 'Warning: Image not found'}", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api") - return jsonify({ - "success": False, - "error": "Image not found" - }), 400 - - try: - journal.send("Running script 'getRepoInfo.py'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - # Ejecutamos el script "getRepoInfo.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 'getRepoInfo.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 getRepoInfo.py', 'desc':'Result OK (ReturnCode: 0)'}", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api") - return jsonify({ - "success": True, - "output": json.loads(result.stdout) - }), 200 - else: - journal.send(f"Script 'getRepoInfo.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 getRepoInfo.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 'getRepoInfo.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 getRepoInfo.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 - - -# --------------------------------------------------------- - - -# 4 - Endpoint "Actualizar Información del Repositorio" (SINCRONO): -@app.route("/ogrepository/v1/images", methods=['PUT']) -def update_repo_info(): - """ Este endpoint actualiza la información del repositorio y de la papelera, reflejándola en los archivos "repoinfo.json" y "trashinfo.josn". - Para ello, ejecuta el script "updateRepoInfo.py", que a su vez ejecuta el script "updateTrashInfo.py". - """ - journal.send("Running endpoint 'Actualizar Información del Repositorio'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - - try: - journal.send("Running script 'updateRepoInfo.py'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - # Ejecutamos el script "updateRepoInfo.py" (sin parámetros), y almacenamos el resultado: - result = subprocess.run(['python3', f"{script_path}/updateRepoInfo.py"], check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding='UTF8') - - # Evaluamos el resultado de la ejecución, y devolvemos una respuesta: - if result.returncode == 0: - journal.send("Script 'updateRepoInfo.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 updateRepoInfo.py', 'desc':'Result OK (ReturnCode: 0)'}", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api") - return jsonify({ - "success": True, - "output": "Repository info updated successfully" - }), 200 - else: - journal.send(f"Script 'updateRepoInfo.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 updateRepoInfo.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 'updateRepoInfo.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 updateRepoInfo.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 - - -# --------------------------------------------------------- - - -# 5 - Endpoint "Chequear Integridad de Imagen" (SINCRONO): -@app.route("/ogrepository/v1/status/images/", methods=['GET']) -def check_image(imageId): - """ Este endpoint comprueba la integridad de la imagen especificada como parámetro, - comparando el tamaño y el hash MD5 del último MB con los valores almacenados en los archivos "size" y "sum". - Para ello, ejecuta el script "checkImage.py", con el nombre de la imagen como único parámetro. - """ - journal.send("Running endpoint 'Chequear Integridad de Imagen'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - - # Obtenemos el nombre y la extensión de la imagen: - param_dict = get_image_params(imageId, "repo") - - # Evaluamos los parámetros obtenidos, para construir la llamada al script, o para devover un error si no se ha encontrado la imagen: - if param_dict: - cmd = ['python3', f"{script_path}/checkImage.py", f"{param_dict['name']}.{param_dict['extension']}"] - else: - journal.send("Image not found (inexistent or deleted)", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - journal.send("{'component':'ogRepo', 'severity':'WARNING', 'http_code':'400', 'operation':'Run endpoint check_image', 'desc':'Warning: Image not found'}", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api") - return jsonify({ - "success": False, - "error": "Image not found (inexistent or deleted)" - }), 400 - - try: - journal.send("Running script 'checkImage.py'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - # Ejecutamos el script "checkImage.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 'checkImage.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 checkImage.py', 'desc':'Result OK (ReturnCode: 0)'}", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api") - if "Error" in result.stdout: - return jsonify({ - "success": True, - "output": "Image file didn't pass the Integrity Check" - }), 200 - else: - return jsonify({ - "success": True, - "output": "Image file passed the Integrity Check correctly" - }), 200 - else: - journal.send(f"Script 'checkImage.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 checkImage.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 'checkImage.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 checkImage.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 - - -# --------------------------------------------------------- - - -# 6 - Endpoint "Eliminar una Imagen" (SINCRONO): -@app.route("/ogrepository/v1/images/", methods=['DELETE']) -def delete_image(imageId): - """ Este endpoint elimina la imagen especificada como parámetro (y todos sus archivos asociados), - moviéndolos a la papelera o eliminándolos permanentemente (dependiendo del parámetro "method"). - Para ello, ejecuta el script "deleteImage.py", con el nombre de la imagen como primer parámetro, y el parámetro opcional "-p". - """ - journal.send("Running endpoint 'Eliminar una Imagen'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - - # Obtenemos el nombre y la extensión de la imagen: - param_dict = get_image_params(imageId, "repo") - # Almacenams el método de eliminación ("trash" o "permanent") - method = request.values.get('method') - - # Evaluamos los parámetros obtenidos, para construir la llamada al script, o para devover un error si no se ha encontrado la imagen: - if param_dict: - if method == "permanent": - cmd = ['python3', f"{script_path}/deleteImage.py", f"{param_dict['name']}.{param_dict['extension']}", '-p'] - elif method == "trash": - cmd = ['python3', f"{script_path}/deleteImage.py", f"{param_dict['name']}.{param_dict['extension']}"] - else: - journal.send("Incorrect method (must be 'permanent' or 'trash')", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - journal.send("{'component':'ogRepo', 'severity':'WARNING', 'http_code':'400', 'operation':'Run endpoint delete_image', 'desc':'Warning: Incorrect method (must be permanent or trash)'}", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api") - return jsonify({ - "success": False, - "error": "Incorrect method (must be 'permanent' or 'trash')" - }), 400 - else: - journal.send("Image not found (inexistent or deleted)", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - journal.send("{'component':'ogRepo', 'severity':'WARNING', 'http_code':'400', 'operation':'Run endpoint delete_image', 'desc':'Warning: Image not found (inexistent or deleted)'}", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api") - return jsonify({ - "success": False, - "error": "Image not found (inexistent or deleted)" - }), 400 - - try: - journal.send("Running script 'deleteImage.py'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - # Ejecutamos el script "deleteImage.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 una respuesta: - if result.returncode == 0: - journal.send("Script 'deleteImage.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 deleteImage.py', 'desc':'Result OK (ReturnCode: 0)'}", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api") - return jsonify({ - "success": True, - "output": "Image deleted successfully" - }), 200 - else: - journal.send(f"Script 'deleteImage.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 deleteImage.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 'deleteImage.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 deleteImage.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 - - -# --------------------------------------------------------- - - -# 7 - Endpoint "Recuperar una Imagen" (SINCRONO): -@app.route("/ogrepository/v1/trash/images", methods=['POST']) -def recover_image(): - """ Este endpoint recupera la imagen especificada como parámetro (y todos sus archivos asociados), - moviéndolos nuevamente al repositorio de imágenes, desde la papelera. - Para ello, ejecuta el script "recoverImage.py", con el nombre de la imagen como único parámetro. - """ - journal.send("Running endpoint 'Recuperar una Imagen'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - - # Almacenamos el parámetro "ID_img" (enviado por JSON): - json_data = json.loads(request.data) - 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") - - # Evaluamos los parámetros obtenidos, para construir la llamada al script, o para devover un error si no se ha encontrado la imagen: - if param_dict: - cmd = ['python3', f"{script_path}/recoverImage.py", f"{param_dict['name']}.{param_dict['extension']}"] - else: - journal.send("Image not found (inexistent or recovered previously)", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - journal.send("{'component':'ogRepo', 'severity':'WARNING', 'http_code':'400', 'operation':'Run endpoint recover_image', 'desc':'Warning: Image not found (inexistent or recovered previously)'}", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api") - return jsonify({ - "success": False, - "error": "Image not found (inexistent or recovered previously)" - }), 400 - - try: - journal.send("Running script 'recoverImage.py'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - # Ejecutamos el script "recoverImage.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 una respuesta: - if result.returncode == 0: - journal.send("Script 'recoverImage.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 recoverImage.py', 'desc':'Result OK (ReturnCode: 0)'}", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api") - return jsonify({ - "success": True, - "output": "Image recovered successfully" - }), 200 - else: - journal.send(f"Script 'recoverImage.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 recoverImage.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 'recoverImage.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 recoverImage.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 - - -# --------------------------------------------------------- - - -# 8 - Endpoint "Eliminar una Imagen de la Papelera" (SINCRONO): -@app.route("/ogrepository/v1/trash/images/", methods=['DELETE']) -def delete_trash_image(imageId): - """ Este endpoint elimina permanentemente la imagen especificada como parámetro (y todos sus archivos asociados), desde la papelera. - Para ello, ejecuta el script "deleteTrashImage.py", con el nombre de la imagen como único parámetro. - """ - journal.send("Running endpoint 'Eliminar una Imagen de la Papelera'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - - # Obtenemos el nombre y la extensión de la imagen: - param_dict = get_image_params(imageId, "trash") - - # Evaluamos los parámetros obtenidos, para construir la llamada al script, o para devover un error si no se ha encontrado la imagen: - if param_dict: - cmd = ['python3', f"{script_path}/deleteTrashImage.py", f"{param_dict['name']}.{param_dict['extension']}"] - else: - journal.send("Image not found at trash", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - journal.send("{'component':'ogRepo', 'severity':'WARNING', 'http_code':'400', 'operation':'Run endpoint delete_trash_image', 'desc':'Warning: Image not found at trash'}", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api") - return jsonify({ - "success": False, - "error": "Image not found at trash" - }), 400 - - try: - journal.send("Running script 'deleteTrashImage.py'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - # Ejecutamos el script "deleteTrashImage.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 una respuesta: - if result.returncode == 0: - journal.send("Script 'deleteTrashImage.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 deleteTrashImage.py', 'desc':'Result OK (ReturnCode: 0)'}", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api") - return jsonify({ - "success": True, - "output": "Image deleted successfully" - }), 200 - else: - journal.send(f"Script 'deleteTrashImage.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 deleteTrashImage.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 'deleteTrashImage.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 deleteTrashImage.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 - - -# --------------------------------------------------------- - - -# 9 - Endpoint "Importar una Imagen" (ASINCRONO): -@app.route("/ogrepository/v1/repo/images", methods=['POST']) -def import_image(): - """ Este endpoint importa la imagen especificada como primer parámetro (y todos sus archivos asociados), desde un servidor remoto al servidor local. - Para ello, ejecuta el script "importImage.py", con el nombre de la imagen como primer parámetro, - la IP o hostname del servidor remoto como segundo parámetro, y el usuario con el que conectar al servidor como tercer parámetro. - """ - journal.send("Running endpoint 'Importar 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_name = json_data.get("image") - remote_ip = json_data.get("repo_ip") - remote_user = json_data.get("user") - - # Comprobamos la conexión con el equipo remoto, y si falla salimos del endpoint, retornando un error: - connection_OK = check_remote_connection(remote_ip, remote_user) - if connection_OK == False: - journal.send("Can't connect to remote server", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - journal.send("{'component':'ogRepo', 'severity':'ERROR', 'http_code':'400', 'operation':'Run endpoint import_image', 'desc':'Unable to connect to remote server'}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api") - return jsonify({ - "success": False, - "exception": "Can't connect to remote server" - }), 400 - - # Construimos la ruta de la imagen: - image_file_path = f"{repo_path}{image_name}" - - # Comprobamos si la imagen remota no existe o está bloqueada, en cuyos casos salimos del endpoint y retornamos el error correspondiente: - check_image = check_remote_image(remote_ip, remote_user, image_file_path) - if check_image == "Remote image not found": - journal.send("Remote image not found", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - journal.send("{'component':'ogRepo', 'severity':'WARNING', 'http_code':'400', 'operation':'Run endpoint import_image', 'desc':'Warning: Remote image not found'}", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api") - return jsonify({ - "success": False, - "exception": "Remote image not found" - }), 400 - elif check_image == "Remote image is locked": - journal.send("Remote image is locked", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - journal.send("{'component':'ogRepo', 'severity':'WARNING', 'http_code':'400', 'operation':'Run endpoint import_image', 'desc':'Warning: Remote image is locked'}", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api") - return jsonify({ - "success": False, - "exception": "Remote image is locked" - }), 400 - - # Construimos la llamada al script: - cmd = ['python3', f"{script_path}/importImage.py", image_file_path, remote_ip, remote_user] - - try: - journal.send("Running script 'importImage.py'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - # Ejecutamos el script "importImage.py" (con los parámetros almacenados), y almacenamos el resultado (pero sin esperar a que termine el proceso): - result = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding='UTF8') - - # Generamos el ID para identificar el trabajo asíncrono: - job_id = f"TransferImage_{''.join(random.choice('0123456789abcdef') for char in range(8))}" - journal.send(f"JOB ID generated ({job_id})", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - - # Evaluamos el resultado de la ejecución, y devolvemos una respuesta: - 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") - threading.Thread(target=check_lock_local, args=(image_file_path, job_id,)).start() - - # Informamos que la imagen se está importando, y salimos del endpoint: - return jsonify({ - "success": True, - "output": "Importing image...", - "job_id": job_id - }), 200 - else: - journal.send("Script 'importImage.py' result KO (Image import failed)", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - journal.send("{'component':'ogRepo', 'severity':'ERROR', 'http_code':'500', 'operation':'Run script importImage.py', 'desc':'Result KO (Error: Image import failed)'}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api") - return jsonify({ - "success": False, - "error": "Image import failed" - }), 500 - except subprocess.CalledProcessError as error: - journal.send(f"Script 'importImage.py' result KO (Process Exception: {str(error)})", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - journal.send(f"{{'component':'ogRepo', 'severity':'ERROR', 'http_code':'500', 'operation':'Run script importImage.py', 'desc':'Result KO (Process Exception: {str(error)})'}}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api") - return jsonify({ - "success": False, - "process exception": str(error) - }), 500 - except Exception as error_description: - journal.send(f"Script 'importImage.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 importImage.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 - - -# --------------------------------------------------------- - - -# 10 - Endpoint "Hacer backup de una Imagen" (ASINCRONO): -@app.route("/ogrepository/v1/repo/images", methods=['PUT']) -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. - """ - journal.send("Running endpoint 'Hacer backup de 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") - remote_ip = json_data.get("repo_ip") - remote_user = json_data.get("user") - remote_path = json_data.get("remote_path") - - # Si el úitimo carácter de "remote_path" no es una barra, la añadimos: - if remote_path[-1] != "/": - remote_path = f"{remote_path}/" - - # 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 ruta de la imagen, o para devover un error si no se ha encontrado la imagen (o si está bloqueada): - if param_dict: - image_name = f"{param_dict['name']}.{param_dict['extension']}" - - # Comprobamos si el archivo que bloquea la imagen existe, llamando a la función "check_file_exists": - image_lock_exists = check_file_exists(f"{repo_path}{image_name}.lock") - - # Si la imagen existe pero está bloqueada, devolvemos un error: - if image_lock_exists == True: - journal.send("Image is locked", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - journal.send("{'component':'ogRepo', 'severity':'WARNING', 'http_code':'400', 'operation':'Run endpoint backup_image', 'desc':'Warning: Image is locked'}", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api") - return jsonify({ - "success": False, - "exception": "Image is locked" - }), 400 - 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 backup_image', 'desc':'Warning: Image not found'}", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api") - return jsonify({ - "success": False, - "error": "Image not found" - }), 400 - - # Comprobamos la conexión con el equipo remoto, y si falla salimos del endpoint, retornando un error: - connection_OK = check_remote_connection(remote_ip, remote_user) - - if connection_OK == False: - journal.send("Can't connect to remote server", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - journal.send("{'component':'ogRepo', 'severity':'ERROR', 'http_code':'400', 'operation':'Run endpoint backup_image', 'desc':'Unable to connect to remote host'}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api") - return jsonify({ - "success": False, - "exception": "Can't connect to remote host" - }), 400 - - # Construimos la llamada al script: - cmd = ['python3', f"{script_path}/backupImage.py", image_name, remote_ip, remote_user, remote_path] - - try: - journal.send("Running script 'backupImage.py'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - # Ejecutamos el script "backupImage.py" (con los parámetros almacenados), y almacenamos el resultado (pero sin esperar a que termine el proceso): - result = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding='UTF8') - - # Generamos el ID para identificar el trabajo asíncrono: - job_id = f"BackupImage_{''.join(random.choice('0123456789abcdef') for char in range(8))}" - journal.send(f"JOB ID generated ({job_id})", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - - # Evaluamos el resultado de la ejecución, y devolvemos una respuesta: - if result.returncode is None: - journal.send("Script 'backupImage.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 backupImage.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_remote_backup" en un hilo paralelo - # (para que compruebe si la imagen se ha acabado de copiar exitosamente): - journal.send("Calling function 'check_remote_backup'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - threading.Thread(target=check_remote_backup, args=(image_name, remote_ip, remote_user, remote_path, job_id,)).start() - - # Informamos que la imagen se está exportando, y salimos del endpoint: - return jsonify({ - "success": True, - "output": "Making image backup...", - "job_id": job_id - }), 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") - return jsonify({ - "success": False, - "error": "Backup image failed" - }), 500 - except subprocess.CalledProcessError as error: - journal.send(f"Script 'backupImage.py' result KO (Process Exception: {str(error)})", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - journal.send(f"{{'component':'ogRepo', 'severity':'ERROR', 'http_code':'500', 'operation':'Run script backupImage.py', 'desc':'Result KO (Process Exception: {str(error_)})'}}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api") - return jsonify({ - "success": False, - "process exception": str(error) - }), 500 - except Exception as error_description: - if "exit status 5" in str(error_description): - journal.send("Image already exists on remote host", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - journal.send("{'component':'ogRepo', 'severity':'WARNING', 'http_code':'400', 'operation':'Run script backupImage.py', 'desc':'Warning: Image already exists on remote host'}", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api") - return jsonify({ - "success": False, - "exception": "Image already exists on remote host" - }), 400 - else: - journal.send(f"Script 'backupImage.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 backupImage.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 - - -# --------------------------------------------------------- - - -# 11 - Endpoint "Crear archivos auxiliares" (ASINCRONO): -@app.route("/ogrepository/v1/images/torrentsum", methods=['POST']) -def create_torrent_sum(): - """ Este endpoint crea los archivos ".size", ".sum", ".full.sum" y ".torrent" para la imagen que recibe como parámetro. - Para ello, ejecuta el script "createTorrentSum.py", con el nombre de la imagen como único parámetro. - """ - journal.send("Running endpoint 'Crear archivos auxiliares'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - - # Almacenamos los parámetros enviados en el JSON: - json_data = json.loads(request.data) - image_name = json_data.get("image") - - # Comprobamos si la imagen existe, llamando a la función "check_file_exists": - image_exists = check_file_exists(f"{repo_path}{image_name}") - - # Si la imagen no existe, retornamos un error y salimos del endpoint: - if image_exists == False: - 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 create_torrent_sum', 'desc':'Warning: Image not found'}", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api") - return jsonify({ - "success": False, - "exception": "Image not found" - }), 400 - - # Construimos la ruta de la imagen (relativa a "repo_path"): - image_file_path = image_name - - # Construimos la llamada al script: - cmd = ['python3', f"{script_path}/createTorrentSum.py", image_file_path] - - try: - journal.send("Running script 'createTorrentSum.py'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - # Ejecutamos el script "createTorrentSum.py" (con los parámetros almacenados), y almacenamos el resultado (pero sin esperar a que termine el proceso): - result = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding='UTF8') - - # Generamos el ID para identificar el trabajo asíncrono: - job_id = f"CreateAuxiliarFiles_{''.join(random.choice('0123456789abcdef') for char in range(8))}" - journal.send(f"JOB ID generated ({job_id})", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - - # Evaluamos el resultado de la ejecución, y devolvemos una respuesta: - if result.returncode is None: - journal.send("Script 'createTorrentSum.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 createTorrentSum.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_aux_files" en un hilo paralelo - # (para que compruebe si se han creado todos los archivos auxiliares exitosamente): - journal.send("Calling function 'check_aux_files'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - threading.Thread(target=check_aux_files, args=(f"{repo_path}{image_file_path}", job_id,)).start() - - # Informamos que los archivos auxiliares se están creando, y salimos del endpoint: - return jsonify({ - "success": True, - "output": "Creating auxiliar files...", - "job_id": job_id - }), 200 - else: - journal.send(f"Script 'createTorrentSum.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 createTorrentSum.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: - if "exit status 2" in str(error_description): - 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 script createTorrentSum.py', 'desc':'Warning: Image not found'}", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api") - return jsonify({ - "success": False, - "exception": "Image not found" - }), 400 - elif "exit status 3" in str(error_description): - journal.send("Image is locked", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - journal.send("{'component':'ogRepo', 'severity':'WARNING', 'http_code':'400', 'operation':'Run script createTorrentSum.py', 'desc':'Warning: Image is locked'}", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api") - return jsonify({ - "success": False, - "exception": "Image is locked" - }), 400 - else: - journal.send(f"Script 'createTorrentSum.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 createTorrentSum.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 - - -# --------------------------------------------------------- - - -# 12 - Endpoint "Enviar paquete Wake On Lan" (SINCRONO): -@app.route("/ogrepository/v1/wol", methods=['POST']) -def send_wakeonlan(): - """ Este endpoint envía un paquete mágico Wake On Lan a la dirección MAC especificada, a través de la IP de broadcast especificadac. - Para ello, ejecuta el script "sendWakeOnLan.py", con la IP de broadcast como primer parámetro, y la MAC como segundo parámetro. - """ - journal.send("Running endpoint 'Enviar paquete Wake On Lan'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - - # Almacenamos los parámetros enviados en el JSON: - json_data = json.loads(request.data) - broadcast_ip = json_data.get("broadcast_ip") - mac = json_data.get("mac") - - try: - journal.send("Running script 'sendWakeOnLan.py'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - # Ejecutamos el script "sendWakeOnLan.py" (con los parámetros almacenados), y almacenamos el resultado: - result = subprocess.run(['python3', f"{script_path}/sendWakeOnLan.py", broadcast_ip, mac], check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding='UTF8') - - # Evaluamos el resultado de la ejecución, y devolvemos una respuesta: - if result.returncode == 0: - journal.send("Script 'sendWakeOnLan.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 sendWakeOnLan.py', 'desc':'Result OK (ReturnCode: 0)'}", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api") - return jsonify({ - "success": True, - "output": "Wake On Lan packet sended successfully" - }), 200 - else: - journal.send(f"Script 'sendWakeOnLan.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 sendWakeOnLan.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 'sendWakeOnLan.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 sendWakeOnLan.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 - - -# --------------------------------------------------------- - - -# 13 - Endpoint "Enviar una Imagen mediante UDPcast" (ASINCRONO): -@app.route("/ogrepository/v1/udpcast", methods=['POST']) -def send_udpcast(): - """ Este endpoint envía mediante UDPcast la imagen que recibe como primer parámetro, con los datos de transferencia que recibe en los demás parámetros. - Para ello, ejecuta el script "sendFileMcast.py", con la imagen como primer parámetro, y los demás en una cadena (como segundo parámetro). - """ - journal.send("Running endpoint 'Enviar una Imagen mediante UDPcast'...", 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") - port = json_data.get("port") - method = json_data.get("method") - ip = json_data.get("ip") - bitrate = json_data.get("bitrate") - nclients = json_data.get("nclients") - maxtime = json_data.get("maxtime") - - # Obtenemos el nombre y la extensión de la imagen (y el subdirectorio de OU, si fuera el caso): - param_dict = get_image_params(image_id, "repo") - - # Evaluamos los parámetros obtenidos, para construir la llamada al script, o para devover un error si no se ha encontrado la imagen: - if param_dict: - cmd = ['python3', f"{script_path}/sendFileMcast.py", f"{param_dict['name']}.{param_dict['extension']}", f"{port}:{method}:{ip}:{bitrate}:{nclients}:{maxtime}"] - 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 send_udpcast', 'desc':'Warning: Image not found'}", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api") - return jsonify({ - "success": False, - "error": "Image not found" - }), 400 - - try: - journal.send("Running script 'sendFileMcast.py'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - # Ejecutamos el script "sendFileMcast.py" (con los parámetros almacenados), y almacenamos el resultado (pero sin esperar a que termine el proceso): - result = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding='UTF8') - - # Comprobamos si está corriendo el proceso correspondiente de "udp-sender" (esperando 5 segundos para darle tiempo a iniciarse): - sleep(5) - process_running = search_process('udp-sender', f"{param_dict['name']}.{param_dict['extension']}") - - # Evaluamos el resultado de la ejecución, y devolvemos una respuesta: - if result.returncode is None and process_running == True: - journal.send("Script 'sendFileMcast.py' result OK (ReturnCode: None), and process running", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - journal.send("{'component':'ogRepo', 'severity':'INFO', 'http_code':'200', 'operation':'Run script sendFileMcast.py', 'desc':'Result OK (ReturnCode: None), and process running'}", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api") - return jsonify({ - "success": True, - "output": "Sending image..." - }), 200 - else: - journal.send("Script 'sendFileMcast.py' result KO (Image send failed)", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - journal.send("{'component':'ogRepo', 'severity':'ERROR', 'http_code':'500', 'operation':'Run script sendFileMcast.py', 'desc':'Result KO (Image send failed)'}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api") - return jsonify({ - "success": False, - "error": "Image send failed" - }), 500 - except subprocess.CalledProcessError as error: - journal.send(f"Script 'sendFileMcast.py' result KO (Process Exception: {str(error)})", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - journal.send(f"{{'component':'ogRepo', 'severity':'ERROR', 'http_code':'500', 'operation':'Run script sendFileMcast.py', 'desc':'Result KO (Process Exception: {str(error)})'}}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api") - return jsonify({ - "success": False, - "process exeption": str(error) - }), 500 - except Exception as error_description: - journal.send(f"Script 'sendFileMcast.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 sendFileMcast.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 - - -# --------------------------------------------------------- - - -# 14 - Endpoint "Enviar una Imagen mediante UFTP" (ASINCRONO): -@app.route("/ogrepository/v1/uftp", methods=['POST']) -def send_uftp(): - """ Este endpoint envía mediante UFTP la imagen que recibe como primer parámetro, con los datos de transferencia que recibe en los demás parámetros. - Para ello, ejecuta el script "sendFileUFTP.py", con la imagen como primer parámetro, y los demás en una cadena (como segundo parámetro). - NOTA: Es necesario que los clientes se hayan puesto en escucha previamente (ejecutando el script "listenUFTPD.py"). - """ - journal.send("Running endpoint 'Enviar una Imagen mediante UFTP'...", 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") - port = json_data.get("port") - ip = json_data.get("ip") - bitrate = json_data.get("bitrate") - - # Obtenemos el nombre y la extensión de la imagen (y el subdirectorio de OU, si fuera el caso): - param_dict = get_image_params(image_id, "repo") - - # Evaluamos los parámetros obtenidos, para construir la llamada al script, o para devover un error si no se ha encontrado la imagen: - if param_dict: - cmd = ['python3', f"{script_path}/sendFileUFTP.py", f"{param_dict['name']}.{param_dict['extension']}", f"{port}:{ip}:{bitrate}"] - 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 send_uftp', 'desc':'Warning: Image not found'}", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api") - return jsonify({ - "success": False, - "error": "Image not found" - }), 400 - - try: - journal.send("Running script 'sendFileUFTP.py'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - # Ejecutamos el script "sendFileUFTP.py" (con los parámetros almacenados), y almacenamos el resultado (pero sin esperar a que termine el proceso): - result = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding='UTF8') - - # Comprobamos si está corriendo el proceso correspondiente de "uftp" (esperando 5 segundos para darle tiempo a iniciarse): - sleep(5) - process_running = search_process('uftp', f"{param_dict['name']}.{param_dict['extension']}") - - # Evaluamos el resultado de la ejecución, y devolvemos una respuesta: - if result.returncode is None and process_running == True: - journal.send("Script 'sendFileUFTP.py' result OK (ReturnCode: None), and process running", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - journal.send("{'component':'ogRepo', 'severity':'INFO', 'http_code':'200', 'operation':'Run script sendFileUFTP.py', 'desc':'Result OK (ReturnCode: None), and process running'}", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api") - return jsonify({ - "success": True, - "output": "Sending image..." - }), 200 - else: - journal.send("Script 'sendFileUFTP.py' result KO (Image send failed)", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - journal.send("{'component':'ogRepo', 'severity':'ERROR', 'http_code':'500', 'operation':'Run script sendFileUFTP.py', 'desc':'Result KO (Image send failed)'}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api") - return jsonify({ - "success": False, - "error": "Image send failed" - }), 500 - except subprocess.CalledProcessError as error: - journal.send(f"Script 'sendFileUFTP.py' result KO (Process Exception: {str(error)})", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - journal.send(f"{{'component':'ogRepo', 'severity':'ERROR', 'http_code':'500', 'operation':'Run script sendFileUFTP.py', 'desc':'Result KO (Process Exception: {str(error)})'}}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api") - return jsonify({ - "success": False, - "process exeption": str(error) - }), 500 - except Exception as error_description: - journal.send(f"Script 'sendFileUFTP.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 sendFileUFTP.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 - - -# --------------------------------------------------------- - - -# 15 - Endpoint "Enviar una Imagen mediante P2P" (ASINCRONO): -@app.route("/ogrepository/v1/p2p", methods=['POST']) -def send_p2p(): - """ Este endpoint inicia el tracker "bttrack" y el seeder "bittornado", en el directorio de imágenes (sirviendo todas las imágenes). - Para ello, ejecuta los scripts "runTorrentTracker.py" y "runTorrentSeeder.py", que no reciben parámetros. - """ - journal.send("Running endpoint 'Enviar una Imagen mediante P2P'...", 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") - - # Obtenemos el nombre y la extensión de la imagen (y el subdirectorio de OU, si fuera el caso): - param_dict = get_image_params(image_id, "repo") - - # Evaluamos los parámetros obtenidos, para construir las llamadas a los scripts, o para devover un error si no se ha encontrado la imagen: - if param_dict: - cmd_tracker = ['sudo', 'python3', f"{script_path}/runTorrentTracker.py"] # Este script si que requiere ser ejecutado con "sudo" - cmd_seeder = ['sudo', 'python3', f"{script_path}/runTorrentSeeder.py"] # Este script si que requiere ser ejecutado con "sudo" - base_path = repo_path.rstrip('/') # Le quito la última barra para poder buscar correctamente en los procesos - 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 send_p2p', 'desc':'Warning: Image not found'}", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api") - return jsonify({ - "success": False, - "error": "Image not found" - }), 400 - - # Ejecutamos los scripts "runTorrentSeeder.py" y "runTorrentSeeder.py", que no reciben parámetros. - # NOTA: No almacenamos la salida ni comprobamos los errores, porque los procesos quedarán corriendo hasta que se finalicen manualmente, - # por lo que no podemos comprobar el returncode (luego comprobaremos si los procesos se han iniciado correctamente). - journal.send("Running script 'runTorrentTracker.py'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - subprocess.Popen(cmd_tracker) - - journal.send("Running script 'runTorrentSeeder.py'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - subprocess.Popen(cmd_seeder) - - # Comprobamos si el tracker y el seeder están corriendo, y si apuntan al directorio que le hemos pasado - # (esperamos 10 segundos antes de hacerlo, porque los procesos no se inician inmediatamente): - sleep(10) - tracker_running = search_process('bttrack', base_path) - seeder_running = search_process('btlaunchmany', base_path) - - # Evaluamos las comprobaciones anteriores, para devolver la respuesta que corresponda: - if tracker_running and seeder_running: - journal.send("Scripts 'runTorrentTracker.py' and 'runTorrentSeeder.py' results OK (ReturnCodes: None), and processes running", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - journal.send("{'component':'ogRepo', 'severity':'INFO', 'http_code':'200', 'operation':'Run scripts runTorrentTracker.py and runTorrentSeeder.py', 'desc':'Results OK (ReturnCodes: None), and processes running'}", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api") - return jsonify({ - "success": True, - "output": "Tracker and Seeder serving image correctly" - }), 200 - else: - journal.send("Scripts 'runTorrentTracker.py' and 'runTorrentSeeder.py' results KO (Tracker or/and Seeder not runnig)", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - journal.send("{'component':'ogRepo', 'severity':'ERROR', 'http_code':'500', 'operation':'Run scripts runTorrentTracker.py and runTorrentSeeder.py', 'desc':'Results KO (Tracker or/and Seeder not runnig)'}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api") - return jsonify({ - "success": False, - "error": "Tracker or Seeder (or both) not running" - }), 500 - - -# --------------------------------------------------------- - - -# 16 - Endpoint "Ver Estado de Transmisiones UDPcast" (SINCRONO): -@app.route("/ogrepository/v1/udpcast", methods=['GET']) -def get_udpcast_info(): - """ Este endpoint devuelve información sobre los procesos de "udp-sender" activos, en formato json, - lo que en la práctica permite comprobar las transferencias UDPcast activas. - Para ello, ejecuta el script "getUDPcastInfo.py", que no recibe parámetros. - """ - journal.send("Running endpoint 'Ver Estado de Transmisiones UDPcast'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - - try: - journal.send("Running script 'getUDPcastInfo.py'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - # Ejecutamos el script "getUDPcastInfo.py", y almacenamos el resultado: - result = subprocess.run(['python3', f"{script_path}/getUDPcastInfo.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 'getUDPcastInfo.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 getUDPcastInfo.py', 'desc':'Result OK (ReturnCode: 0)'}", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api") - return jsonify({ - "success": True, - "output": json.loads(result.stdout) - }), 200 - else: - journal.send(f"Script 'getUDPcastInfo.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 getUDPcastInfo.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: - if "exit status 1" in str(error_description): - journal.send("No UDPcast active transmissions", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - journal.send("{'component':'ogRepo', 'severity':'WARNING', 'http_code':'400', 'operation':'Run script getUDPcastInfo.py', 'desc':'Warning: No UDPcast active transmissions'}", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api") - return jsonify({ - "success": False, - "exception": "No UDPCast active transmissions" - }), 400 - else: - journal.send(f"Script 'getUDPcastInfo.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 getUDPcastInfo.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 - - -# --------------------------------------------------------- - - -# 17 - Endpoint "Ver Estado de Transmisiones UFTP" (SINCRONO): -@app.route("/ogrepository/v1/uftp", methods=['GET']) -def get_uftp_info(): - """ Este endpoint devuelve información sobre los procesos de "uftp" activos, en formato json, - lo que en la práctica permite comprobar las transferencias UFTP activas. - Para ello, ejecuta el script "getUFTPInfo.py", que no recibe parámetros. - """ - journal.send("Running endpoint 'Ver Estado de Transmisiones UFTP'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - - try: - journal.send("Running script 'getUFTPInfo.py'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - # Ejecutamos el script "getUFTPInfo.py", y almacenamos el resultado: - result = subprocess.run(['python3', f"{script_path}/getUFTPInfo.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 'getUFTPInfo.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 getUFTPInfo.py', 'desc':'Result OK (ReturnCode: 0)'}", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api") - return jsonify({ - "success": True, - "output": json.loads(result.stdout) - }), 200 - else: - journal.send(f"Script 'getUFTPInfo.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 getUFTPInfo.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: - if "exit status 1" in str(error_description): - journal.send("No UFTP active transmissions", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - journal.send("{'component':'ogRepo', 'severity':'WARNING', 'http_code':'400', 'operation':'Run script getUFTPInfo.py', 'desc':'Warning: No UFTP active transmissions'}", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api") - return jsonify({ - "success": False, - "exception": "No UFTP active transmissions" - }), 400 - else: - journal.send(f"Script 'getUFTPInfo.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 getUFTPInfo.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 - - -# --------------------------------------------------------- - - -# 18 - Endpoint "Cancelar Transmisión UDPcast" (SINCRONO): -@app.route("/ogrepository/v1/udpcast/images/", methods=['DELETE']) -def stop_udpcast(imageId): - """ Este endpoint cancela la transmisión UDPcast de la imagen que recibe como parámetro, finalizando el proceso "udp-sender" asociado. - Para ello, ejecuta el script "stopUDPcast.py", pasándole el nombre de la imagen. - """ - journal.send("Running endpoint 'Cancelar Transmisión UDPcast'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - - # Obtenemos el nombre y la extensión de la imagen: - param_dict = get_image_params(imageId, "repo") - - # Evaluamos los parámetros obtenidos, para construir la llamada al script, o para devover un error si no se ha encontrado la imagen: - if param_dict: - cmd = ['python3', f"{script_path}/stopUDPcast.py", f"{param_dict['name']}.{param_dict['extension']}"] - 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 stop_udpcast', 'desc':'Warning: Image not found'}", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api") - return jsonify({ - "success": False, - "error": "Image not found" - }), 400 - - try: - journal.send("Running script 'stopUDPcast.py'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - # Ejecutamos el script "stopUDPcast.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 una respuesta: - if result.returncode == 0: - journal.send("Script 'stopUDPcast.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 stopUDPcast.py', 'desc':'Result OK (ReturnCode: 0)'}", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api") - return jsonify({ - "success": True, - "output": "Image transmission canceled successfully" - }), 200 - else: - journal.send(f"Script 'stopUDPcast.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 stopUDPcast.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: - if "exit status 3" in str(error_description): - journal.send("No UDPCast active transmissions for specified image", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - journal.send("{'component':'ogRepo', 'severity':'WARNING', 'http_code':'400', 'operation':'Run script stopUDPcast.py', 'desc':'Warning: No UDPCast active transmissions for specified image'}", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api") - return jsonify({ - "success": False, - "exception": "No UDPCast active transmissions for specified image" - }), 400 - elif "exit status 4" in str(error_description): - journal.send("Script 'stopUDPcast.py' result KO (Unexpected error checking UDPcast transmissions)", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - journal.send("{'component':'ogRepo', 'severity':'ERROR', 'http_code':'500', 'operation':'Run script stopUDPcast.py', 'desc':'Result KO (Unexpected error checking UDPcast transmissions)'}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api") - return jsonify({ - "success": False, - "exception": "Unexpected error checking UDPcast transmissions" - }), 500 - elif "exit status 5" in str(error_description): - journal.send("Script 'stopUDPcast.py' result KO (Unexpected error finalizing UDPcast transmission)", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - journal.send("{'component':'ogRepo', 'severity':'ERROR', 'http_code':'500', 'operation':'Run script stopUDPcast.py', 'desc':'Result KO (Unexpected error finalizing UDPcast transmission)'}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api") - return jsonify({ - "success": False, - "exception": "Unexpected error finalizing UDPcast transmission" - }), 500 - else: - journal.send(f"Script 'stopUDPcast.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 stopUDPcast.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 - - -# --------------------------------------------------------- - - -# 19 - Endpoint "Cancelar Transmisión UFTP" (SINCRONO): -@app.route("/ogrepository/v1/uftp/images/", methods=['DELETE']) -def stop_uftp(imageId): - """ Este endpoint cancela la transmisión UFTP de la imagen que recibe como parámetro, finalizando el proceso "uftp" asociado. - Para ello, ejecuta el script "stopUFTP.py", pasándole el nombre de la imagen. - """ - journal.send("Running endpoint 'Cancelar Transmisión UFTP'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - - # Obtenemos el nombre y la extensión de la imagen (y el subdirectorio de OU, si fuera el caso): - param_dict = get_image_params(imageId, "repo") - - # Evaluamos los parámetros obtenidos, para construir la llamada al script, o para devover un error si no se ha encontrado la imagen: - if param_dict: - cmd = ['python3', f"{script_path}/stopUFTP.py", f"{param_dict['name']}.{param_dict['extension']}"] - 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 stop_uftp', 'desc':'Warning: Image not found'}", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api") - return jsonify({ - "success": False, - "error": "Image not found" - }), 400 - - try: - journal.send("Running script 'stopUFTP.py'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - # Ejecutamos el script "stopUFTP.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 una respuesta: - if result.returncode == 0: - journal.send("Script 'stopUFTP.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 stopUFTP.py', 'desc':'Result OK (ReturnCode: 0)'}", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api") - return jsonify({ - "success": True, - "output": "Image transmission canceled successfully" - }), 200 - else: - journal.send(f"Script 'stopUFTP.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 stopUFTP.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: - if "exit status 3" in str(error_description): - journal.send("No UFTP active transmissions for specified image", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - journal.send("{'component':'ogRepo', 'severity':'WARNING', 'http_code':'400', 'operation':'Run script stopUFTP.py', 'desc':'Warning: No UFTP active transmissions for specified image'}", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api") - return jsonify({ - "success": False, - "exception": "No UFTP active transmissions for specified image" - }), 400 - elif "exit status 4" in str(error_description): - journal.send("Script 'stopUFTP.py' result KO (Unexpected error checking UFTP transmissions)", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - journal.send("{'component':'ogRepo', 'severity':'ERROR', 'http_code':'500', 'operation':'Run script stopUFTP.py', 'desc':'Result KO (Unexpected error checking UFTP transmissions)'}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api") - return jsonify({ - "success": False, - "exception": "Unexpected error checking UFTP transmissions" - }), 500 - elif "exit status 5" in str(error_description): - journal.send("Script 'stopUFTP.py' result KO (Unexpected error finalizing UFTP transmission)", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - journal.send("{'component':'ogRepo', 'severity':'ERROR', 'http_code':'500', 'operation':'Run script stopUFTP.py', 'desc':'Result KO (Unexpected error finalizing UFTP transmission)'}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api") - return jsonify({ - "success": False, - "exception": "Unexpected error finalizing UFTP transmission" - }), 500 - else: - journal.send(f"Script 'stopUFTP.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 stopUFTP.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 - - -# --------------------------------------------------------- - - -# 20 - Endpoint "Cancelar Transmisiones P2P" (SINCRONO): -@app.route("/ogrepository/v1/p2p", methods=['DELETE']) -def stop_p2p(): - """ Este endpoint cancela las transmisiones P2P activas, finalizando los procesos "btlaunchmany.bittornado" (seeder) y "bttrack" (tracker). - Para ello, ejecuta el script "stopP2P.py", que no recibe parámetros. - """ - journal.send("Running endpoint 'Cancelar Transmisiones P2P'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") - - try: - 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") - journal.send("{'component':'ogRepo', 'severity':'INFO', 'http_code':'200', 'operation':'Run script stopP2P.py', 'desc':'Result OK (ReturnCode: 0)'}", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api") - return jsonify({ - "success": True, - "output": "P2P transmissions canceled successfully" - }), 200 - else: - journal.send(f"Script 'stopP2P.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 stopP2P.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 'stopP2P.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 stopP2P.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_server/debian/oggit/usr/share/doc/oggit/changelog.gz b/api_server/debian/oggit/usr/share/doc/oggit/changelog.gz deleted file mode 100644 index ef9dddb..0000000 Binary files a/api_server/debian/oggit/usr/share/doc/oggit/changelog.gz and /dev/null differ diff --git a/api_server/debian/oggit/usr/share/doc/oggit/copyright b/api_server/debian/oggit/usr/share/doc/oggit/copyright deleted file mode 100644 index 5d5fd7e..0000000 --- a/api_server/debian/oggit/usr/share/doc/oggit/copyright +++ /dev/null @@ -1,43 +0,0 @@ -Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ -Source: -Upstream-Name: ogboot -Upstream-Contact: - -Files: - * -Copyright: - - -License: GPL-3.0+ - -Files: - debian/* -Copyright: - 2025 vagrant -License: GPL-3.0+ - -License: GPL-3.0+ - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - . - This package is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - . - You should have received a copy of the GNU General Public License - along with this program. If not, see . -Comment: - On Debian systems, the complete text of the GNU General - Public License version 3 can be found in "/usr/share/common-licenses/GPL-3". - -# Please also look if there are files or directories which have a -# different copyright/license attached and list them here. -# Please avoid picking licenses with terms that are more restrictive than the -# packaged work, as it may make Debian's contributions unacceptable upstream. -# -# If you need, there are some extra license texts available in two places: -# /usr/share/debhelper/dh_make/licenses/ -# /usr/share/common-licenses/ diff --git a/api_server/debian/rules b/api_server/debian/rules deleted file mode 100755 index fce8f73..0000000 --- a/api_server/debian/rules +++ /dev/null @@ -1,33 +0,0 @@ -#!/usr/bin/make -f - -# See debhelper(7) (uncomment to enable). -# Output every command that modifies files on the build system. -#export DH_VERBOSE = 1 - - -# See FEATURE AREAS in dpkg-buildflags(1). -#export DEB_BUILD_MAINT_OPTIONS = hardening=+all - -# See ENVIRONMENT in dpkg-buildflags(1). -# Package maintainers to append CFLAGS. -#export DEB_CFLAGS_MAINT_APPEND = -Wall -pedantic -# Package maintainers to append LDFLAGS. -#export DEB_LDFLAGS_MAINT_APPEND = -Wl,--as-needed - - -%: - dh $@ - - -%: - dh $@ - -# Ejecutar composer install durante la fase de construcción -override_dh_auto_build: - - -# dh_make generated override targets. -# This is an example for Cmake (see ). -#override_dh_auto_configure: -# dh_auto_configure -- \ -# -DCMAKE_LIBRARY_PATH=$(DEB_HOST_MULTIARCH) diff --git a/api_server/install_deps.sh b/api_server/install_deps.sh deleted file mode 100755 index ede15e2..0000000 --- a/api_server/install_deps.sh +++ /dev/null @@ -1,33 +0,0 @@ -#!/bin/bash - -#!/bin/bash - -set -e - -if [ ! -f "/etc/apt/sources.list.d/opengnsys.sources" ] ; then - - cat > /etc/apt/sources.list.d/opengnsys.sources <