ogrepository/bin/updateRepoInfo.py

244 lines
13 KiB
Python

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Este script actualiza la información de las imágenes del repositorio, reflejándola en el archivo "/opt/opengnsys/ogrepository/etc/repoinfo.json",
añadiendo información de las imágenes nuevas, y borrando la información de las imágenes que fueron eliminadas.
La información es obtenida desde archivos "<imagen_name>.info", que originalmente eran eliminados,
pero que en esta versión son renombrados a "<imagen_name>.info.checked", obteniendo la misma funcionalidad.
Al acabar, llama al script "updateTrashInfo.py", para actualizar también la información de la papelera del repositorio.
No recibe ningún parámetro, y es llamado por el propio ogRepoitory cada vez que se elimina una imagen (desde el script "deleteImage.py").
"""
# --------------------------------------------------------------------------------------------
# IMPORTS
# --------------------------------------------------------------------------------------------
import os
import sys
import json
import subprocess
import shutil
from systemd import journal
# --------------------------------------------------------------------------------------------
# VARIABLES
# --------------------------------------------------------------------------------------------
repo_path = '/opt/opengnsys/ogrepository/images' # En este caso, no lleva barra final
info_file = '/opt/opengnsys/ogrepository/etc/repoinfo.json'
update_trash_script = '/opt/opengnsys/ogrepository/bin/updateTrashInfo.py'
# --------------------------------------------------------------------------------------------
# FUNCTIONS
# --------------------------------------------------------------------------------------------
def create_empty_json():
""" Esta función crea el archivo "repoinfo.json", con la estructura básica que debe contener.
Evidentemente, solo es llamada cuando no existe el archivo.
"""
# Creamos un diccionario con la estructura básica que debe tener el archivo json:
json_data = {"directory": repo_path, "images": []}
# Abrimos el archivo json en modo escritura (creándolo si no existe, como es el caso),
# y le añadimos la estructura almacenada en el diccionario "json_data":
with open(info_file, 'w') as file:
json.dump(json_data, file, indent=2)
def check_files():
""" Esta función recorre el directorio de imágenes, para buscar archivos con extensión ".img".
Llama a la función "add_to_json" para que esta añada información de las imágenes no bloqueadas (sin archivo ".lock"),
que además tengan un archivo ".info" asociado (siempre que este no se haya modificado antes que la propia imagen).
Originalmente eliminaba los archivos ".info" después de extraer la información, pero ahora los renombra (agrega ".checked").
"""
# Iteramos recursivamente todos los archivos y directorios de "/opt/opengnsys/ogrepository/images",
# y luego iteramos todos los archivos encontrados (en una iteración anidada):
for root, dirs, files in os.walk(repo_path):
for file in files:
# Si la imagen actual tiene extensión ".img", construimos la ruta completa ("img_path"):
if file.endswith(".img"):
img_path = os.path.join(root, file)
# Si existe un archivo ".lock" asociado a la imagen actual, pasamos a la siguiente:
if os.path.exists(f"{img_path}.lock"):
continue
# Comprobamos si existe un archivo ".info" asociado a la imagen actual:
info_file = f"{img_path}.info"
if os.path.exists(info_file):
# Si la fecha de modificación del archivo ".info" es anterior a la de la imagen, lo eliminamos (porque estará desactualizado):
if os.path.getmtime(info_file) < os.path.getmtime(img_path):
os.remove(info_file)
print(f"Warning: Deleted outdated file {info_file}")
# En caso contrario, almacenamos el contenido del archivo ".info" (del tipo "PARTCLONE:LZOP:EXTFS:8500000:Ubuntu_20") en la variable "data":
else:
with open(info_file, 'r') as file:
info_data = file.read()
# Almacenamos el contenido de los archivos ".size", ".sum" y ".full.sum":
with open(f"{img_path}.size", 'r') as file:
size = file.read().strip('\n')
with open(f"{img_path}.sum", 'r') as file:
_sum = file.read().strip('\n')
with open(f"{img_path}.full.sum", 'r') as file:
fullsum = file.read().strip('\n')
# Llamamos a la función "add_to_json", para que inserte la información de la imagen en el archivo json
# (pasándole el nombre de la imagen, la extensión, y los datos extraídos del archivo ".info"):
img_name = os.path.relpath(img_path, repo_path)
add_to_json(os.path.splitext(img_name)[0], os.path.splitext(img_name)[1][1:], info_data, size, _sum, fullsum)
# Renombramos el archivo ".info" a ".info.checked", para que ya no se añada la información que contiene
# (originalmente se eliminaba el archivo, pero creo que es mejor mantenerlo):
os.rename(info_file, f"{info_file}.checked")
def add_to_json(image_name, image_type, data, size, _sum, fullsum):
""" Esta función añade al archivo "repoinfo.json" la información de las imágenes que aun no ha sido introducida en él (imágenes nuevas, básicamente).
"""
# Almacenamos el contenido de la variable "data" (del tipo "PARTCLONE:LZOP:EXTFS:8500000:Ubuntu_20") en variables separadas:
clonator, compressor, fstype, datasize, client = data.split(":")
# Creamos un diccionario con los datos de la imagen, que luego insertaremos en el json:
json_data = {
"name": image_name,
"type": image_type.lower(),
"clientname": client.strip('\n'), # Eliminamos el salto de linea
"clonator": clonator.lower(),
"compressor": compressor.lower(),
"filesystem": fstype.upper(),
"datasize": int(datasize) * 1024, # Convertimos el valor a bytes (desde KB)
"size": int(size),
"sum": _sum,
"fullsum": fullsum
}
# Almacenamos el contenido del archivo "repoinfo.json" en la variable "info_data"
# (y si no tiene contenido creamos la estructura básica):
if os.path.getsize(info_file) > 0:
with open(info_file, 'r') as file:
info_data = json.load(file)
else:
info_data = {"directory": repo_path, "images": []}
# Comprobamos si las claves "info_data" (o sea, del archivo json) son las correctas:
if set(info_data.keys()) == {"directory", "images"}:
# Actualizamos la información de las imágenes:
img_index = next((i for i, img in enumerate(info_data["images"]) if img["name"] == image_name), None)
if img_index is not None:
# Update if image data changes
if info_data["images"][img_index] != json_data:
info_data["images"][img_index] = json_data
else:
# Append a new entry
info_data["images"].append(json_data)
# Si las claves de "info_data" no son las correctas, creamos toda la estructura:
else:
info_data = {"directory": repo_path, "images": [json_data]}
# Sobreescribimos el archivo "repoinfo.json", con el contenido de "info_data" (que estará actualizado):
with open(info_file, 'w') as file:
json.dump(info_data, file, indent=2)
def remove_from_json():
""" Esta función carga el contenido del archivo "repoinfo.json", y comprueba la existencia de las imágenes especificadas allí.
Elimina las claves correspondientes a imágenes inexistentes, y posteriormente sobreescribe el archivo "repoinfo.json".
"""
# Almacenamos el contenido del archivo "repoinfo.json", en la variable "json_data":
with open(info_file, 'r') as file:
json_data = json.load(file)
# Iteramos las imágenes de la clave "images" de "json_data", construimos sus rutas y comprobamos su existencia
# (si no existen, las eliminamos de "json_data"):
for i, image in enumerate(json_data.get("images", [])):
img_name = image["name"]
img_type = image["type"]
if img_type != "dir":
img_name = f"{img_name}.{img_type}"
img_path = os.path.join(repo_path, img_name)
if not os.path.exists(img_path) and not os.path.exists(f"{img_path}.lock"):
json_data["images"].pop(i)
# Sobreescribimos el archivo "repoinfo.json", con el contenido de "json_data"
# (una vez eliminadas las imágenes inexistentes):
with open(info_file, 'w') as file:
json.dump(json_data, file, indent=2)
def update_trash_info():
""" Actualiza la información de la papelera del repositorio, ejecutando el script "updateTrashInfo.py".
Como se ve, es necesario que el script se ejecute como sudo, o dará error.
"""
try:
journal.send("updateRepoInfo.py: Running script 'updateTrashInfo.py'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api")
result = subprocess.run(['sudo', 'python3', update_trash_script], check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
except subprocess.CalledProcessError as error:
journal.send(f"updateRepoInfo.py: 'updateTrashInfo.py' error: {error}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api")
print(f"Error Output: {error.stderr.decode()}")
sys.exit(3)
except Exception as error:
journal.send(f"updateRepoInfo.py: 'updateTrashInfo.py' exception: {error}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api")
print(f"Se ha producido un error inesperado: {error}")
sys.exit(4)
# --------------------------------------------------------------------------------------------
# MAIN
# --------------------------------------------------------------------------------------------
def main():
"""
"""
# Comprobamos si tenemos permisos de escritura sobre el directorio que contiene el archivo "repoinfo.json"
# ("/opt/opengnsys/etc"), y en caso contrario lanzamos una excepción:
if not os.access(os.path.dirname(info_file), os.W_OK):
journal.send(f"updateRepoInfo.py: Cant't access to '{info_file}' directory", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api")
raise PermissionError(f"Cannot access {info_file}")
# Comprobamos si existe el archivo "repoinfo.json", y en caso contrario lo creamos:
if not os.path.exists(info_file):
journal.send("updateRepoInfo.py: Creating empty json file...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api")
print("Creating empty json file...")
create_empty_json()
# Comprobamos si tenemos permisos de escritura sobre el archivo, y en caso contrario lanzamos un error:
if not os.access(info_file, os.W_OK):
journal.send(f"updateRepoInfo.py: Cant't write on '{info_file}'", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api")
raise PermissionError(f"Cannot access {info_file}")
# Llamamos a la función "check_files", para añadir al archivo json las imágenes aun no añadidas
# (que son las que tienen asociado un archivo ".info", aun no renombrado o eliminado):
journal.send("updateRepoInfo.py: Checking file images...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api")
print("Checking file images...")
check_files()
# Llamamos a la función "remove_from_json", para eliminar del archivo json las imágenes que fueron eliminadas del repositorio
# (solo si el archivo tiene contenido, o dará error):
if os.path.getsize(info_file) > 0:
journal.send("updateRepoInfo.py: Removing deleted images from json file...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api")
print("Removing deleted images...")
remove_from_json()
# Actualizamos la información de la papelera, ejecutando el script "updateTrashInfo.py":
journal.send("updateRepoInfo.py: Updating trash info...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api")
print("Updating Trash Info...")
update_trash_info()
# --------------------------------------------------------------------------------------------
if __name__ == "__main__":
main()
# --------------------------------------------------------------------------------------------