""" Updated Utility Functions for Server_Monitorizare Now using Ansible for remote device management instead of direct HTTP commands """ import logging from functools import wraps from flask import jsonify, request, session from datetime import datetime from ansible_integration import get_ansible_manager logger = logging.getLogger(__name__) class APIError(Exception): """Custom exception for API errors""" def __init__(self, message, status_code=400, details=None): self.message = message self.status_code = status_code self.details = details or {} super().__init__(self.message) def error_response(error_message, status_code=400, details=None): """Create a standardized error response""" response = { 'success': False, 'error': error_message, 'timestamp': datetime.now().isoformat() } if details: response['details'] = details return jsonify(response), status_code def success_response(data=None, message="Success", status_code=200): """Create a standardized success response""" response = { 'success': True, 'message': message, 'timestamp': datetime.now().isoformat() } if data is not None: response['data'] = data return jsonify(response), status_code def require_auth(f): """Decorator to require authentication""" @wraps(f) def decorated_function(*args, **kwargs): # Check for API key in headers api_key = request.headers.get('X-API-Key') from config import get_config config = get_config() if not api_key or api_key != config.API_KEY: logger.warning(f"Unauthorized access attempt from {request.remote_addr}") return error_response("Unauthorized", 401) return f(*args, **kwargs) return decorated_function def require_session_auth(f): """Decorator to require session-based authentication""" @wraps(f) def decorated_function(*args, **kwargs): if 'user_id' not in session: return error_response("Authentication required", 401) return f(*args, **kwargs) return decorated_function def log_request(f): """Decorator to log request details""" @wraps(f) def decorated_function(*args, **kwargs): logger.info(f"{request.method} {request.path} from {request.remote_addr}") try: return f(*args, **kwargs) except APIError as e: logger.error(f"API Error in {f.__name__}: {e.message}") return error_response(e.message, e.status_code, e.details) except Exception as e: logger.exception(f"Unexpected error in {f.__name__}") return error_response("Internal server error", 500) return decorated_function def validate_required_fields(required_fields): """Decorator to validate required fields in JSON request""" def decorator(f): @wraps(f) def decorated_function(*args, **kwargs): if not request.is_json: return error_response("Content-Type must be application/json", 400) data = request.get_json() missing_fields = [field for field in required_fields if field not in data or not data[field]] if missing_fields: return error_response( "Missing required fields", 400, {'missing_fields': missing_fields} ) return f(*args, **kwargs) return decorated_function return decorator def validate_ip_address(ip): """Validate IP address format""" import re pattern = r'^(\d{1,3}\.){3}\d{1,3}$' if not re.match(pattern, ip): return False parts = ip.split('.') return all(0 <= int(part) <= 255 for part in parts) def sanitize_hostname(hostname): """Sanitize hostname to prevent injection""" import re # Allow alphanumeric, dash, and underscore if not re.match(r'^[a-zA-Z0-9_-]+$', hostname): raise APIError("Invalid hostname format", 400) if len(hostname) > 255: raise APIError("Hostname too long", 400) return hostname def setup_logging(config): """Setup logging configuration""" import os from logging.handlers import RotatingFileHandler # Create logs directory if it doesn't exist log_dir = os.path.dirname(config.LOG_FILE) if log_dir and not os.path.exists(log_dir): os.makedirs(log_dir) # Create logger logger = logging.getLogger() logger.setLevel(getattr(logging, config.LOG_LEVEL)) # File handler with rotation file_handler = RotatingFileHandler( config.LOG_FILE, maxBytes=config.LOG_MAX_BYTES, backupCount=config.LOG_BACKUP_COUNT ) file_handler.setFormatter(logging.Formatter( '%(asctime)s - %(name)s - %(levelname)s - %(message)s' )) # Console handler console_handler = logging.StreamHandler() console_handler.setFormatter(logging.Formatter( '%(asctime)s - %(name)s - %(levelname)s - %(message)s' )) logger.addHandler(file_handler) logger.addHandler(console_handler) return logger # ============================================================================ # ANSIBLE-BASED DEVICE MANAGEMENT FUNCTIONS # ============================================================================ def execute_command_on_device(device_hostname: str, command: str, user: str = 'pi'): """ Execute command on a device using Ansible Args: device_hostname: Device hostname from inventory command: Command to execute user: User to execute command as Returns: dict: Execution result with success status and output """ try: ansible_mgr = get_ansible_manager() logger.info(f"Executing command on {device_hostname}: {command}") result = ansible_mgr.execute_command_on_device( device=device_hostname, command=command, user=user ) if result['success']: logger.info(f"Command executed successfully on {device_hostname}") return { "success": True, "result": result['output'], "device": device_hostname, "timestamp": result['timestamp'] } else: logger.error(f"Command failed on {device_hostname}: {result.get('error')}") return { "success": False, "error": result.get('error', 'Unknown error'), "device": device_hostname, "timestamp": result['timestamp'] } except Exception as e: logger.exception(f"Error executing command on {device_hostname}: {e}") return { "success": False, "error": f"Connection error: {str(e)}", "device": device_hostname } def deploy_updates_to_device( device_hostname: str, git_branch: str = 'dev', backup: bool = True, restart: bool = True ): """ Deploy updates to device using Ansible deploy playbook Args: device_hostname: Device hostname from inventory git_branch: Git branch to deploy backup: Whether to backup before deploy restart: Whether to restart service Returns: dict: Deployment result """ try: ansible_mgr = get_ansible_manager() logger.info(f"Starting deployment on {device_hostname} from branch {git_branch}") result = ansible_mgr.deploy_to_devices( target_hosts=device_hostname, git_branch=git_branch, backup=backup, restart=restart ) if result['success']: logger.info(f"Deployment completed successfully on {device_hostname}") return { "success": True, "message": f"Deployed {git_branch} to {device_hostname}", "device": device_hostname, "branch": git_branch, "log_file": result.get('log_file'), "timestamp": result['timestamp'] } else: logger.error(f"Deployment failed on {device_hostname}: {result.get('error')}") return { "success": False, "error": result.get('error', 'Deployment failed'), "device": device_hostname, "timestamp": result['timestamp'] } except Exception as e: logger.exception(f"Error deploying to {device_hostname}: {e}") return { "success": False, "error": f"Deployment error: {str(e)}", "device": device_hostname } def deploy_updates_to_all_devices( git_branch: str = 'dev', backup: bool = True, restart: bool = True ): """ Deploy updates to all devices in the 'prezenta_devices' group Args: git_branch: Git branch to deploy backup: Whether to backup before deploy restart: Whether to restart service Returns: dict: Deployment result for all devices """ try: ansible_mgr = get_ansible_manager() logger.info(f"Starting batch deployment to all devices from branch {git_branch}") result = ansible_mgr.deploy_to_devices( target_hosts='prezenta_devices', git_branch=git_branch, backup=backup, restart=restart ) if result['success']: logger.info(f"Batch deployment completed successfully") return { "success": True, "message": f"Deployed {git_branch} to all devices", "branch": git_branch, "log_file": result.get('log_file'), "timestamp": result['timestamp'] } else: logger.error(f"Batch deployment failed: {result.get('error')}") return { "success": False, "error": result.get('error', 'Batch deployment failed'), "timestamp": result['timestamp'] } except Exception as e: logger.exception(f"Error in batch deployment: {e}") return { "success": False, "error": f"Batch deployment error: {str(e)}" } def system_update_device( device_hostname: str, update_os: bool = False, update_python: bool = True, health_check: bool = True ): """ Perform system update and maintenance on device Args: device_hostname: Device hostname from inventory update_os: Whether to update OS packages update_python: Whether to update Python packages health_check: Whether to perform health check Returns: dict: Update result """ try: ansible_mgr = get_ansible_manager() logger.info(f"Starting system update on {device_hostname}") result = ansible_mgr.system_update( target_hosts=device_hostname, update_os=update_os, update_python=update_python, health_check=health_check ) if result['success']: logger.info(f"System update completed on {device_hostname}") return { "success": True, "message": f"System update completed on {device_hostname}", "device": device_hostname, "log_file": result.get('log_file'), "timestamp": result['timestamp'] } else: logger.error(f"System update failed on {device_hostname}: {result.get('error')}") return { "success": False, "error": result.get('error', 'System update failed'), "device": device_hostname, "timestamp": result['timestamp'] } except Exception as e: logger.exception(f"Error updating system on {device_hostname}: {e}") return { "success": False, "error": f"System update error: {str(e)}", "device": device_hostname } def get_device_facts(device_hostname: str): """ Get facts about a device using Ansible Args: device_hostname: Device hostname from inventory Returns: dict: Device facts and information """ try: ansible_mgr = get_ansible_manager() logger.info(f"Gathering facts for {device_hostname}") result = ansible_mgr.get_device_facts(device_hostname) if result['success']: return { "success": True, "device": device_hostname, "facts": result.get('facts', {}), "timestamp": result['timestamp'] } else: logger.error(f"Failed to get facts for {device_hostname}: {result.get('error')}") return { "success": False, "error": result.get('error', 'Failed to get device facts'), "device": device_hostname } except Exception as e: logger.exception(f"Error getting facts for {device_hostname}: {e}") return { "success": False, "error": f"Error getting device facts: {str(e)}", "device": device_hostname } def get_device_status(device_hostname: str): """ Get device status (uses get_device_facts internally) Args: device_hostname: Device hostname from inventory Returns: dict: Device status information """ facts = get_device_facts(device_hostname) if facts['success']: ansible_facts = facts.get('facts', {}) return { "success": True, "status": { "hostname": device_hostname, "system": ansible_facts.get('ansible_system'), "distribution": ansible_facts.get('ansible_distribution'), "version": ansible_facts.get('ansible_distribution_version'), "ip_address": ansible_facts.get('ansible_default_ipv4', {}).get('address'), "uptime_seconds": ansible_facts.get('ansible_uptime_seconds'), "processor_count": ansible_facts.get('ansible_processor_count'), "memtotal_mb": ansible_facts.get('ansible_memtotal_mb'), "memfree_mb": ansible_facts.get('ansible_memfree_mb'), "timestamp": facts['timestamp'] } } else: return facts def get_execution_logs(limit: int = 10): """Get recent Ansible execution logs""" try: ansible_mgr = get_ansible_manager() logs = ansible_mgr.get_execution_logs(limit) return { "success": True, "logs": logs, "count": len(logs) } except Exception as e: logger.exception(f"Error getting execution logs: {e}") return { "success": False, "error": f"Error getting logs: {str(e)}" }