324 lines
17 KiB
Python
324 lines
17 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
|
|
|
|
|
|
# --------------------------------------------------------------------------------------------
|
|
# 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": [], "ous": []}
|
|
|
|
# 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 extensiones ".img" o ".dsk".
|
|
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/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" o ".dsk", construimos la ruta completa ("img_path"):
|
|
if file.endswith((".img", ".dsk")):
|
|
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 check_dirs():
|
|
""" Esta función recorre el directorio de imágenes, para buscar archivos con nombre "ogimg.info".
|
|
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 "ogimg.info" asociado (pero no sé que tipo de imágenes son esas).
|
|
"""
|
|
# Iteramos recursivamente todos los archivos y directorios de "/opt/opengnsys/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 el nombre del archivo actual es "ogimg.info", construimos su ruta completa ("info_path"), y la ruta del directorio ("img_path"):
|
|
if file == "ogimg.info":
|
|
info_path = os.path.join(root, file)
|
|
img_path = os.path.dirname(info_path)
|
|
# Si existe un archivo ".lock" asociado a la imagen actual, o si la ruta coincide con "repo_path", pasamos a la siguiente:
|
|
if img_path == repo_path or os.path.exists(f"{img_path}.lock"):
|
|
continue
|
|
# Almacenamos el contenido del archivo "ogimg.info", en la variable "lines":
|
|
with open(info_path, 'r') as file:
|
|
lines = file.readlines()
|
|
# Iteramos las lineas obtenidas, para extraer el tipo de sistema de archivos ("fstype") y el tamaño de los datos ("sizedata"),
|
|
# y con ello construimos una cadena "rsync::{fstype}:{sizedata}:" (que almacenamos en "data"):
|
|
for line in lines:
|
|
if line.startswith("# fstype"):
|
|
fstype = line.split("=")[1].strip()
|
|
elif line.startswith("# sizedata"):
|
|
sizedata = line.split("=")[1].strip()
|
|
info_data = f"rsync::{fstype}:{sizedata}:"
|
|
# 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, el tipo "dir"", y los datos extraídos del archivo "ogimg.info"):
|
|
img_name = os.path.relpath(img_path, repo_path)
|
|
add_to_json(img_name, "dir", info_data, size, _sum, fullsum)
|
|
|
|
|
|
|
|
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).
|
|
El procedimiento es diferente para las imágenes "normales" y para las imágenes basadas en OU.
|
|
"""
|
|
# 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(":")
|
|
|
|
# Si la imagen está dentro de una OU (directorio), extraemos el nombre de la OU y de la imagen:
|
|
ou_name = None
|
|
if '/' in image_name:
|
|
ou_name = os.path.dirname(image_name)
|
|
image_name = os.path.basename(image_name)
|
|
|
|
# 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": [], "ous": []}
|
|
|
|
# Comprobamos si las claves "info_data" (o sea, del archivo json) son las correctas:
|
|
if set(info_data.keys()) == {"directory", "images", "ous"}:
|
|
|
|
# Actualizamos la información de las imágenes "normales" (no basadas en OU):
|
|
if ou_name is None:
|
|
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)
|
|
|
|
# Actualizamos la información de las imágenes basadas en OU:
|
|
else:
|
|
ou_index = next((i for i, ou in enumerate(info_data["ous"]) if ou["subdir"] == ou_name), None)
|
|
if ou_index is None:
|
|
# Append a new OU entry
|
|
info_data["ous"].append({"subdir": ou_name, "images": [json_data]})
|
|
else:
|
|
img_index = next((i for i, img in enumerate(info_data["ous"][ou_index]["images"]) if img["name"] == image_name), None)
|
|
if img_index is not None:
|
|
# Update if image data changes
|
|
if info_data["ous"][ou_index]["images"][img_index] != json_data:
|
|
info_data["ous"][ou_index]["images"][img_index] = json_data
|
|
else:
|
|
# Append a new entry
|
|
info_data["ous"][ou_index]["images"].append(json_data)
|
|
|
|
# Si las claves de "info_data" no son las correctas, creamos toda la estructura:
|
|
else:
|
|
if ou_name is None:
|
|
info_data = {"directory": repo_path, "images": [json_data], "ous": []}
|
|
else:
|
|
info_data = {"directory": repo_path, "images": [], "ous": [{"subdir": ou_name, "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)
|
|
|
|
# Iteramos las imágenes de la clave "ous" de "json_data", construimos sus rutas y comprobamos su existencia
|
|
# (si no existen, las eliminamos de "json_data"):
|
|
for j, ou in enumerate(json_data.get("ous", [])):
|
|
ou_name = ou["subdir"]
|
|
ou_path = os.path.join(repo_path, ou_name)
|
|
if not os.path.exists(ou_path):
|
|
json_data["ous"].pop(j)
|
|
else:
|
|
for i, image in enumerate(ou.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(ou_path, img_name)
|
|
if not os.path.exists(img_path) and not os.path.exists(f"{img_path}.lock"):
|
|
ou["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:
|
|
result = subprocess.run(['sudo', 'python3', update_trash_script], check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
|
except subprocess.CalledProcessError as error:
|
|
print(f"Error Output: {error.stderr.decode()}")
|
|
sys.exit(3)
|
|
except Exception as error:
|
|
print(f"Se ha producido un error inesperado: {error}")
|
|
sys.exit(4)
|
|
|
|
|
|
|
|
# --------------------------------------------------------------------------------------------
|
|
# MAIN
|
|
# --------------------------------------------------------------------------------------------
|
|
|
|
|
|
def main():
|
|
"""
|
|
"""
|
|
# 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):
|
|
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):
|
|
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):
|
|
raise PermissionError(f"Cannot access {info_file}")
|
|
|
|
# Llamamos a la función "check_files", para añadir al archivo json las imágenes ".img" o ".dsk" aun no añadidas
|
|
# (que son las que tienen asociado un archivo ".info", aun no renombrado o eliminado):
|
|
print("Checking file images...")
|
|
check_files()
|
|
|
|
# Llamamos a la función "check_dirs", para añadir al archivo json las imágenes aun no añadidas que tienen asociado un archivo "ogimg.info"
|
|
# (no sé qué imágenes son estas, pero de alguna forma están basadas en directorios):
|
|
print("Checking dir images...")
|
|
check_dirs()
|
|
|
|
# Llamamos a la función "remove_from_json", para eliminar del archivo json las imágenes que fueron eliminadas del repositorio:
|
|
if os.path.getsize(info_file) > 0:
|
|
print("Removing deleted images...")
|
|
remove_from_json()
|
|
|
|
# Actualizamos la información de la papelera, ejecutando el script "updateTrashInfo.py"
|
|
# (solo si el archivo tiene contenido, o dará error):
|
|
print("Updating Trash Info...")
|
|
update_trash_info()
|
|
|
|
|
|
# --------------------------------------------------------------------------------------------
|
|
|
|
if __name__ == "__main__":
|
|
main()
|
|
|
|
# --------------------------------------------------------------------------------------------
|