Files
ASE/src/load_ftp_users.py

212 lines
6.7 KiB
Python

#!.venv/bin/python
"""
Script per prelevare dati da MySQL e inviare comandi SITE FTP
"""
import logging
import sys
from ftplib import FTP
import mysql.connector
from utils.config import users_loader as setting
from utils.database.connection import connetti_db
# Configurazione logging
logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s")
logger = logging.getLogger(__name__)
# Configurazione server FTP
FTP_CONFIG = {"host": "localhost", "user": "admin", "password": "batt1l0", "port": 2121}
def connect_ftp() -> FTP:
"""
Establishes a connection to the FTP server using the predefined configuration.
Returns:
FTP: An active FTP connection object.
"""
try:
ftp = FTP()
ftp.connect(FTP_CONFIG["host"], FTP_CONFIG["port"])
ftp.login(FTP_CONFIG["user"], FTP_CONFIG["password"])
logger.info("Connessione FTP stabilita")
return ftp
except Exception as e: # pylint: disable=broad-except
logger.error("Errore connessione FTP: %s", e)
sys.exit(1)
def fetch_data_from_db(connection: mysql.connector.MySQLConnection) -> list[tuple]:
"""
Fetches username and password data from the 'ftp_accounts' table in the database.
Args:
connection (mysql.connector.MySQLConnection): The database connection object.
Returns:
List[Tuple]: A list of tuples, where each tuple contains (username, password).
"""
try:
cursor = connection.cursor()
# Modifica questa query secondo le tue esigenze
query = """
SELECT username, password
FROM ase_lar.ftp_accounts
"""
cursor.execute(query)
results = cursor.fetchall()
logger.info("Prelevate %s righe dal database", len(results))
return results
except mysql.connector.Error as e:
logger.error("Errore query database: %s", e)
return []
finally:
cursor.close()
def fetch_existing_users(connection: mysql.connector.MySQLConnection) -> dict[str, tuple]:
"""
Fetches existing FTP users from virtusers table.
Args:
connection (mysql.connector.MySQLConnection): The database connection object.
Returns:
dict: Dictionary mapping username to (is_enabled, has_matching_password).
is_enabled is True if disabled_at is NULL.
"""
try:
cursor = connection.cursor()
query = """
SELECT ftpuser, disabled_at
FROM ase_lar.virtusers
"""
cursor.execute(query)
results = cursor.fetchall()
# Create dictionary: username -> is_enabled
users_dict = {username: (disabled_at is None) for username, disabled_at in results}
logger.info("Trovati %s utenti esistenti in virtusers", len(users_dict))
return users_dict
except mysql.connector.Error as e:
logger.error("Errore query database virtusers: %s", e)
return {}
finally:
cursor.close()
def send_site_command(ftp: FTP, command: str) -> bool:
"""
Sends a SITE command to the FTP server.
Args:
ftp (FTP): The FTP connection object.
command (str): The SITE command string to send (e.g., "ADDU username password").
Returns:
bool: True if the command was sent successfully, False otherwise.
"""
try:
# Il comando SITE viene inviato usando sendcmd
response = ftp.sendcmd(f"SITE {command}")
logger.info("Comando SITE %s inviato. Risposta: %s", command, response)
return True
except Exception as e: # pylint: disable=broad-except
logger.error("Errore invio comando SITE %s: %s", command, e)
return False
def main():
"""
Main function to connect to the database, fetch FTP user data, and synchronize users to FTP server.
This function is idempotent - it can be run multiple times safely:
- If user exists and is enabled: skips
- If user exists but is disabled: enables it (SITE ENAU)
- If user doesn't exist: creates it (SITE ADDU)
"""
logger.info("Avvio script caricamento utenti FTP (idempotente)")
cfg = setting.Config()
# Connessioni
db_connection = connetti_db(cfg)
ftp_connection = connect_ftp()
try:
# Preleva utenti da sincronizzare
users_to_sync = fetch_data_from_db(db_connection)
if not users_to_sync:
logger.warning("Nessun utente da sincronizzare nel database ftp_accounts")
return
# Preleva utenti già esistenti
existing_users = fetch_existing_users(db_connection)
added_count = 0
enabled_count = 0
skipped_count = 0
error_count = 0
# Processa ogni utente
for row in users_to_sync:
username, password = row
if username in existing_users:
is_enabled = existing_users[username]
if is_enabled:
# Utente già esiste ed è abilitato - skip
logger.info("Utente %s già esiste ed è abilitato - skip", username)
skipped_count += 1
else:
# Utente esiste ma è disabilitato - riabilita
logger.info("Utente %s esiste ma è disabilitato - riabilito con SITE ENAU", username)
ftp_site_command = f"enau {username}"
if send_site_command(ftp_connection, ftp_site_command):
enabled_count += 1
else:
error_count += 1
else:
# Utente non esiste - crea
logger.info("Utente %s non esiste - creazione con SITE ADDU", username)
ftp_site_command = f"addu {username} {password}"
if send_site_command(ftp_connection, ftp_site_command):
added_count += 1
else:
error_count += 1
logger.info(
"Elaborazione completata. Aggiunti: %s, Riabilitati: %s, Saltati: %s, Errori: %s",
added_count,
enabled_count,
skipped_count,
error_count
)
except Exception as e: # pylint: disable=broad-except
logger.error("Errore generale: %s", e)
finally:
# Chiudi connessioni
try:
ftp_connection.quit()
logger.info("Connessione FTP chiusa")
except Exception as e: # pylint: disable=broad-except
logger.error("Errore chiusura connessione FTP: %s", e)
try:
db_connection.close()
logger.info("Connessione MySQL chiusa")
except Exception as e: # pylint: disable=broad-except
logger.error("Errore chiusura connessione MySQL: %s", e)
if __name__ == "__main__":
main()