Add more documentation

ticket-769
Vadim vtroshchinskiy 2024-09-18 20:18:59 +02:00
parent b53c3875a2
commit 49a1e18c31
1 changed files with 223 additions and 51 deletions

View File

@ -78,7 +78,25 @@ class NTFSImplementation(Enum):
class NTFSLibrary:
"""
A library for managing NTFS filesystems.
Attributes:
logger (logging.Logger): Logger for the class.
implementation (NTFSImplementation): The implementation to use for mounting NTFS filesystems.
"""
def __init__(self, implementation):
"""
Initializes the instance with the given implementation.
Args:
implementation: The implementation to be used by the instance.
Attributes:
logger (logging.Logger): Logger instance for the class, set to debug level.
implementation: The implementation provided during initialization.
"""
self.logger = logging.getLogger("NTFSLibrary")
self.logger.setLevel(logging.DEBUG)
self.implementation = implementation
@ -87,11 +105,35 @@ class NTFSLibrary:
None
def create_filesystem(self, device, label):
"""
Creates an NTFS filesystem on the specified device with the given label.
Args:
device (str): The device path where the NTFS filesystem will be created.
label (str): The label to assign to the NTFS filesystem.
Returns:
None
Logs:
Logs the creation process with the device and label information.
"""
self.logger.info(f"Creating NTFS in {device} with label {label}")
subprocess.run(["/usr/sbin/mkntfs", device, "-Q", "-L", label])
def mount_filesystem(self, device, mountpoint):
"""
Mounts a filesystem on the specified mountpoint using the specified NTFS implementation.
Args:
device (str): The device path to be mounted (e.g., '/dev/sda1').
mountpoint (str): The directory where the device will be mounted.
Raises:
ValueError: If the NTFS implementation is unknown.
"""
self.logger.info(f"Mounting {device} in {mountpoint} using implementation {self.implementation}")
if self.implementation == NTFSImplementation.Kernel:
subprocess.run(["/usr/bin/mount", "-t", "ntfs3", device, mountpoint], check = True)
@ -117,11 +159,18 @@ class NTFSLibrary:
return hex_str
def modify_uuid(self, device, uuid):
"""Modifica el UUID de un sistema de archivos NTFS
"""
Modify the UUID of an NTFS device.
This function changes the UUID of the specified NTFS device to the given UUID.
It reads the current UUID from the device, logs the change, and writes the new UUID.
Args:
device (_type_): Dispositivo
uuid (_type_): UUID nuevo (8 bytes)
device (str): The path to the NTFS device file.
uuid (str): The new UUID to be set, in hexadecimal string format.
Raises:
IOError: If there is an error opening or writing to the device file.
"""
ntfs_uuid_offset = 48
@ -143,6 +192,30 @@ class OpengnsysGitLibrary:
"""Libreria de git"""
def __init__(self, require_cache = True, ntfs_implementation = NTFSImplementation.Kernel):
"""
Initializes the Git library for OpenGnsys.
Args:
require_cache (bool): Indicates whether a cache partition is required. Defaults to True.
ntfs_implementation (NTFSImplementation): Specifies the NTFS implementation to use. Defaults to NTFSImplementation.Kernel.
Raises:
RequirementException: If the cache partition is required but cannot be mounted.
Attributes:
logger (logging.Logger): Logger instance for the Git library.
mounts (list): List of mounted filesystems.
repo_user (str): Username for the repository.
repo_image_path (str): Path to the repository images.
ntfs_implementation (NTFSImplementation): NTFS implementation being used.
cache_dir (str): Directory for the cache.
default_ignore_list (list): List of default paths to ignore.
fully_ignored_dirs (list): List of directories to fully ignore.
kernel_args (dict): Parsed kernel command line arguments.
repo_server (str): Server address for the repository.
debug_check_for_untracked_files (bool): Flag to check for untracked files for debugging purposes.
"""
self.logger = logging.getLogger("OpengnsysGitLibrary")
self.logger.setLevel(logging.DEBUG)
self.logger.debug(f"Initializing. Cache = {require_cache}, ntfs = {ntfs_implementation}")
@ -320,13 +393,13 @@ class OpengnsysGitLibrary:
f.write("\n")
def _parse_kernel_cmdline(self):
"""Obtener parámetros de configuración de la linea de comandos del kernel
"""Parse the kernel arguments to obtain configuration parameters in Oglive
Opengnsys nos pasa parametros por linea de comando del kernel, por ejemplo:
OpenGnsys passes data in the kernel arguments, for example:
[...] group=Aula_virtual ogrepo=192.168.2.1 oglive=192.168.2.1 [...]
Returns:
dict: Diccionario de clave/valor de parámetros
dict: Dict of configuration parameters and their values.
"""
params = {}
self.logger.debug("Parsing kernel parameters")
@ -343,6 +416,18 @@ class OpengnsysGitLibrary:
return params
def _is_filesystem(self, path):
"""
Check if the given path is a filesystem root.
This method reads the '/proc/mounts' file to determine if the specified
path is a mount point (i.e., a filesystem root).
Args:
path (str): The path to check.
Returns:
bool: True if the path is a filesystem root, False otherwise.
"""
with open('/proc/mounts', 'r') as mounts:
for mount in mounts:
parts = mount.split()
@ -358,13 +443,13 @@ class OpengnsysGitLibrary:
def _mklostandfound(self, path):
"""Recrear el lost+found si es necesario.
"""Recreate the lost+found if necessary.
Cuando clonamos en el raíz de un sistema de archivos, al limpiar los contenidos,
eliminamos el lost+found. Este es un directorio especial que requiere el uso de
una herramienta para recrearlo.
When cloning at the root of a filesystem, cleaning the contents
removes the lost+found directory. This is a special directory that requires the use of
a tool to recreate it.
Puede fallar en caso de que el sistema de archivos no lo necesite.
It may fail if the filesystem does not need it. We consider this harmless and ignore it.
"""
if self._is_filesystem(path):
curdir = os.getcwd()
@ -459,16 +544,25 @@ class OpengnsysGitLibrary:
self.logger.debug(f"stderr: {result.stderr}")
def _grub_install(self, boot_device, root_directory):
"""Instalar grub
"""
Install GRUB bootloader on the specified boot device.
This method checks for the presence of GRUB 2.x and GRUB 1.x installers
and attempts to install the appropriate version. If neither installer is
found, a RequirementException is raised.
Args:
device (str): Dispositivo de arranque
root_directory (str): Punto de montaje de sistema raiz
boot_device (str): The device on which to install the GRUB bootloader (e.g., '/dev/sda').
root_directory (str): The root directory where GRUB files should be installed.
Raises:
RequirementException: Si falta binario de GRUB
"""
RequirementException: If neither GRUB 2.x nor GRUB 1.x installer is found.
subprocess.CalledProcessError: If the GRUB installation command fails.
Logs:
Debug information about the installation process, including the return code,
stdout, and stderr of the GRUB installation command.
"""
if os.path.exists("/usr/sbin/grub2-install"):
self.logger.debug("Installing Grub 2.x (NOT IMPLEMENTED)")
elif os.path.exists("/usr/sbin/grub-install"):
@ -483,7 +577,19 @@ class OpengnsysGitLibrary:
raise RequirementException("Couldn't find /usr/sbin/grub2-install or /usr/sbin/grub-install")
def _efi_install(self, boot_device, root_directory):
"""Instalar EFI"""
"""
Install EFI data on the specified boot device.
Copies EFI data from a metadata directory within the root directory
to the specified boot device. It logs the process of installing the EFI data.
Args:
boot_device (str): The path to the boot device where EFI data will be installed.
root_directory (str): The root directory containing the metadata and EFI data.
Raises:
shutil.Error: If an error occurs during the copying of the EFI data.
"""
self.logger.info(f"Instalando datos EFI en {boot_device}")
meta_dir = os.path.join(root_directory, ".opengnsys-metadata")
@ -493,6 +599,19 @@ class OpengnsysGitLibrary:
def _find_boot_device(self):
"""
Searches for the EFI boot partition on the system.
This method scans the system's partitions to locate the EFI boot partition,
which is identified by the GUID "C12A7328-F81F-11D2-BA4B-00A0C93EC93B".
Returns:
str: The device node of the EFI partition if found, otherwise None.
Logs:
- Debug messages indicating the progress of the search.
- A warning message if the EFI partition is not found.
"""
disks = []
self.logger.debug("Looking for EFI partition")
@ -539,6 +658,29 @@ class OpengnsysGitLibrary:
def _runBashFunction(self, function, arguments):
"""
Executes an OpenGnsys bash function with given arguments.
This method creates a temporary bash script that sources all `.lib` files in a specific directory,
writes the specified bash function and its arguments to the script, makes the script executable,
and then runs the script. The output and errors from the script execution are captured and logged.
This is a temporary migration convenience function, it won't be present once the rest of the
code is migrated to Python.
Args:
function (str): The name of the bash function to execute.
arguments (list): A list of arguments to pass to the bash function.
Returns:
str: The standard output from the executed bash function.
Logs:
- Debug information about the bash function and arguments being run.
- The path of the temporary file created.
- The command being executed.
- The standard output and standard error from the script execution.
"""
# Create a temporary file
self.logger.debug(f"Running bash function: {function} {arguments}")
@ -568,11 +710,8 @@ class OpengnsysGitLibrary:
self.logger.debug(f"STDOUT: {output}")
self.logger.debug(f"STDERR: {result.stderr}")
# temp_file.delete()
return output
# os.system(temp_file.name)
def _getOgRepository(self, name):
@ -624,33 +763,33 @@ class OpengnsysGitLibrary:
return results
def _create_metadata(self, path):
"""Calcular metadatos para un filesystem
"""Calculate metadata for a filesystem
Aquí recorremos todo el sistema de archivos para:
Here we traverse the entire filesystem to:
1. Encontrar directorios vacíos y rellenarlos para que git los conserve.
2. Obtener todas las ACLs
3. Obtener todos los atributos extendidos.
4. Renombrar archivos .gitignore
5. Buscar puntos de montaje y obtener información sobre ellos
6. Metadatos adicionales, como el tipo de arranque
7. NTFS secaudit, que debe realizarse al final del proceso porque hay
que desmontar el filesystem.
1. Find empty directories and fill them so that git preserves them.
2. Obtain all ACLs.
3. Obtain all extended attributes.
4. Rename .gitignore files.
5. Find mount points and obtain information about them.
6. Additional metadata, such as the boot type.
7. NTFS secaudit, which must be performed at the end of the process because the filesystem needs to be unmounted.
Para archivos vacíos, generamos una lista que podemos usar después para eliminar los archivos
.opengnsys-keep. Esto se hace porque hay casos en los que un archivo inesperado puede causar
problemas. Por ejemplo, sshfs por defecto se niega a montar cosas en un directorio que contiene
archivos.
For empty files, we generate a list that we can use later to delete the
.opengnsys-keep files. This is done because there are cases where an unexpected
file can cause problems. For example, sshfs by default refuses to mount things
in a directory that contains files.
Renombramos los archivos .gitignore en subdirectorios porque git los aplicaría a nuestro proceso.
We rename the .gitignore files in subdirectories because git would apply them
to our process.
Escribimos todos los datos en JSON para asegurarnos de que no hay problemas con espacios, fines de
linea ni otros caracteres especiales. Esto también asegura una entrada por linea, lo que podemos
usar para acelerar el rendimiento, usando el git para obtener la diferencia entre un estado anterior
y el actual.
We write all data in JSON to ensure there are no issues with spaces, line endings,
or other special characters. This also ensures one entry per line, which we can use
to speed up performance by using git to get the difference between a previous state
and the current one.
Args:
path (str): Ruta base del sistema de archivos
path (str): Base path of the filesystem
"""
self.logger.info(f"Creating metadata for {path}")
@ -884,22 +1023,20 @@ class OpengnsysGitLibrary:
return return_data
def _restore_metadata(self, path, destructive_only=False):
"""Restrauracion de metadatos creados por _createmetadata
"""Restore the metadata created by _create_metadata
Args:
path (str): Ruta destino
destructive_only (bool): Solo restaurar lo que se modifique durante un commit
path (str): Destination path
destructive_only (bool): Only restore what is modified during a commit
Notes:
El git no maneja archivos de tipo dispositivo o socket correctamente. Por tanto,
debemos guardar datos sobre ellos antes del commit, y eliminarlos antes de que
git pueda verlos y confundirse.
Git does not handle device or socket type files correctly. Therefore,
we must save data about them before the commit, and delete them before
git can see them and get confused.
destructive_only=True solo restaura este tipo de metadatos, los que modificamos
en el sistema de archivos real antes del commit. Esto se hace para dejar el
sistema de archivos en el mismo estado que tenia antes del commit.
destructive_only=True only restores the metadata that we modify
in the real file system before the commit. This is done to leave the
file system in the same state it had before the commit.
"""
self.logger.debug("Initializing")
@ -1096,6 +1233,25 @@ class OpengnsysGitLibrary:
file.truncate()
def initRepo(self, device, repo_name):
"""
Initialize a Git repository on a specified device.
This method mounts the device, initializes a Git repository, configures it,
and sets up a remote origin. It handles both NTFS and other filesystem types.
Args:
device (str): The device path to initialize the repository on.
repo_name (str): The name of the repository to be created.
Raises:
RuntimeError: If the .git directory is of an unrecognized file type.
Notes:
- The method mounts the device to /mnt/{device_basename}.
- The .git directory is created in a cache partition and symlinked to the device.
- The repository is initialized and configured, and an initial commit is made.
- The method sets up a remote origin and pushes the initial commit.
"""
self._unmount_device(device)
path = os.path.join("/mnt", os.path.basename(device))
@ -1231,6 +1387,22 @@ class OpengnsysGitLibrary:
repo.git.push("--set-upstream", "origin", repo.head.ref, "--force") # force = True)
def cloneRepo(self, repo_name, destination, boot_device):
"""
Clones a repository to a specified destination and sets up the bootloader.
Args:
repo_name (str): The name of the repository to clone.
destination (str): The destination directory where the repository will be cloned.
boot_device (str): The boot device to install the bootloader.
Raises:
RequirementException: If the repository metadata is incorrect or if the repository's
boot system is incompatible with the current system.
Logs:
Info: Logs the start of the cloning process.
Debug: Logs the repository URL, EFI compatibility of the repository and the system.
"""
self.logger.info(f"Cloning repo: {repo_name} => {destination}")