Files
Server_Monitorizare/ansible_integration.py
Developer cb52e67afa Add Ansible integration for device management and deployment automation
- Added ansible/ directory with playbooks for:
  * deploy.yml: Update applications on devices from git
  * commands.yml: Execute arbitrary commands on devices
  * system_update.yml: OS updates and health checks
  * inventory.ini: Device and group configuration
  * README.md: Comprehensive Ansible guide
  * requirements.txt: Installation instructions

- Added ansible_integration.py: Python module wrapping Ansible operations
- Added utils_ansible.py: Updated utilities using Ansible instead of HTTP commands

Key benefits:
- Idempotent operations with error recovery
- Comprehensive logging and backup
- Multi-device orchestration
- Better reliability and control
- Replaces unreliable direct HTTP command execution
2025-12-18 13:59:48 +02:00

426 lines
14 KiB
Python

"""
Ansible Integration Module for Server_Monitorizare
Provides functions to execute Ansible playbooks and commands on remote devices
"""
import os
import json
import subprocess
import logging
from datetime import datetime
from pathlib import Path
from typing import Dict, List, Any, Optional, Tuple
logger = logging.getLogger(__name__)
class AnsibleManager:
"""Manager class for Ansible operations"""
def __init__(self, ansible_dir: str = None, inventory_file: str = None):
"""
Initialize Ansible manager
Args:
ansible_dir: Path to ansible directory
inventory_file: Path to inventory file
"""
self.ansible_dir = ansible_dir or os.path.join(
os.path.dirname(__file__), 'ansible'
)
self.inventory_file = inventory_file or os.path.join(
self.ansible_dir, 'inventory.ini'
)
self.log_dir = os.path.join(self.ansible_dir, 'logs')
# Create logs directory if it doesn't exist
Path(self.log_dir).mkdir(exist_ok=True)
def _verify_ansible_installed(self) -> bool:
"""Verify Ansible is installed"""
try:
result = subprocess.run(
['ansible', '--version'],
capture_output=True,
text=True,
timeout=10
)
return result.returncode == 0
except Exception as e:
logger.error(f"Ansible not found: {e}")
return False
def deploy_to_devices(
self,
target_hosts: str = 'prezenta_devices',
git_branch: str = 'dev',
backup: bool = True,
restart: bool = True
) -> Dict[str, Any]:
"""
Deploy updates to devices using deploy.yml playbook
Args:
target_hosts: Target host group or specific host
git_branch: Git branch to deploy
backup: Whether to backup before deploy
restart: Whether to restart service
Returns:
dict: Execution result with status and output
"""
if not self._verify_ansible_installed():
return {
'success': False,
'error': 'Ansible is not installed',
'timestamp': datetime.now().isoformat()
}
playbook_file = os.path.join(self.ansible_dir, 'deploy.yml')
if not os.path.exists(playbook_file):
return {
'success': False,
'error': f'Playbook not found: {playbook_file}',
'timestamp': datetime.now().isoformat()
}
# Prepare Ansible command
cmd = [
'ansible-playbook',
'-i', self.inventory_file,
playbook_file,
'-e', f'git_branch={git_branch}',
'-e', f'backup_before_deploy={str(backup).lower()}',
'-e', f'restart_service={str(restart).lower()}',
'--limit', target_hosts,
'-v'
]
logger.info(f"Executing deployment: {' '.join(cmd)}")
try:
log_file = os.path.join(
self.log_dir,
f'deploy_{datetime.now().strftime("%Y%m%d_%H%M%S")}.log'
)
with open(log_file, 'w') as log:
result = subprocess.run(
cmd,
capture_output=True,
text=True,
timeout=600, # 10 minute timeout
cwd=self.ansible_dir
)
log.write(result.stdout)
if result.stderr:
log.write("\n--- STDERR ---\n")
log.write(result.stderr)
success = result.returncode == 0
return {
'success': success,
'returncode': result.returncode,
'output': result.stdout,
'error': result.stderr if not success else None,
'log_file': log_file,
'timestamp': datetime.now().isoformat(),
'target_hosts': target_hosts,
'git_branch': git_branch
}
except subprocess.TimeoutExpired:
return {
'success': False,
'error': 'Deployment timed out after 10 minutes',
'timestamp': datetime.now().isoformat()
}
except Exception as e:
logger.exception(f"Deployment failed: {e}")
return {
'success': False,
'error': f'Deployment failed: {str(e)}',
'timestamp': datetime.now().isoformat()
}
def execute_command_on_device(
self,
device: str,
command: str,
user: str = 'pi'
) -> Dict[str, Any]:
"""
Execute command on specific device
Args:
device: Device hostname or IP
command: Command to execute
user: User to execute command as
Returns:
dict: Execution result
"""
if not self._verify_ansible_installed():
return {
'success': False,
'error': 'Ansible is not installed',
'timestamp': datetime.now().isoformat()
}
playbook_file = os.path.join(self.ansible_dir, 'commands.yml')
if not os.path.exists(playbook_file):
return {
'success': False,
'error': f'Playbook not found: {playbook_file}',
'timestamp': datetime.now().isoformat()
}
cmd = [
'ansible-playbook',
'-i', self.inventory_file,
playbook_file,
'--limit', device,
'-e', f'command={command}',
'-e', f'command_user={user}',
'-v'
]
logger.info(f"Executing command on {device}: {command}")
try:
log_file = os.path.join(
self.log_dir,
f'command_{device}_{datetime.now().strftime("%Y%m%d_%H%M%S")}.log'
)
with open(log_file, 'w') as log:
result = subprocess.run(
cmd,
capture_output=True,
text=True,
timeout=60,
cwd=self.ansible_dir
)
log.write(result.stdout)
if result.stderr:
log.write("\n--- STDERR ---\n")
log.write(result.stderr)
success = result.returncode == 0
return {
'success': success,
'returncode': result.returncode,
'output': result.stdout,
'error': result.stderr if not success else None,
'log_file': log_file,
'timestamp': datetime.now().isoformat(),
'device': device,
'command': command
}
except subprocess.TimeoutExpired:
return {
'success': False,
'error': 'Command execution timed out after 60 seconds',
'timestamp': datetime.now().isoformat()
}
except Exception as e:
logger.exception(f"Command execution failed: {e}")
return {
'success': False,
'error': f'Command execution failed: {str(e)}',
'timestamp': datetime.now().isoformat()
}
def system_update(
self,
target_hosts: str = 'prezenta_devices',
update_os: bool = False,
update_python: bool = True,
health_check: bool = True
) -> Dict[str, Any]:
"""
Execute system update and maintenance
Args:
target_hosts: Target host group
update_os: Whether to update OS packages
update_python: Whether to update Python packages
health_check: Whether to perform health check
Returns:
dict: Execution result
"""
if not self._verify_ansible_installed():
return {
'success': False,
'error': 'Ansible is not installed',
'timestamp': datetime.now().isoformat()
}
playbook_file = os.path.join(self.ansible_dir, 'system_update.yml')
if not os.path.exists(playbook_file):
return {
'success': False,
'error': f'Playbook not found: {playbook_file}',
'timestamp': datetime.now().isoformat()
}
cmd = [
'ansible-playbook',
'-i', self.inventory_file,
playbook_file,
'--limit', target_hosts,
'-e', f'update_os_packages={str(update_os).lower()}',
'-e', f'update_python_packages={str(update_python).lower()}',
'-e', f'perform_health_check={str(health_check).lower()}',
'-v'
]
logger.info(f"Executing system update on {target_hosts}")
try:
log_file = os.path.join(
self.log_dir,
f'system_update_{datetime.now().strftime("%Y%m%d_%H%M%S")}.log'
)
with open(log_file, 'w') as log:
result = subprocess.run(
cmd,
capture_output=True,
text=True,
timeout=1800, # 30 minute timeout
cwd=self.ansible_dir
)
log.write(result.stdout)
if result.stderr:
log.write("\n--- STDERR ---\n")
log.write(result.stderr)
success = result.returncode == 0
return {
'success': success,
'returncode': result.returncode,
'output': result.stdout,
'error': result.stderr if not success else None,
'log_file': log_file,
'timestamp': datetime.now().isoformat(),
'target_hosts': target_hosts
}
except subprocess.TimeoutExpired:
return {
'success': False,
'error': 'System update timed out after 30 minutes',
'timestamp': datetime.now().isoformat()
}
except Exception as e:
logger.exception(f"System update failed: {e}")
return {
'success': False,
'error': f'System update failed: {str(e)}',
'timestamp': datetime.now().isoformat()
}
def get_device_facts(self, device: str) -> Dict[str, Any]:
"""
Get facts about a specific device
Args:
device: Device hostname or IP
Returns:
dict: Device facts and information
"""
if not self._verify_ansible_installed():
return {
'success': False,
'error': 'Ansible is not installed'
}
cmd = [
'ansible',
device,
'-i', self.inventory_file,
'-m', 'setup'
]
try:
result = subprocess.run(
cmd,
capture_output=True,
text=True,
timeout=30,
cwd=self.ansible_dir
)
if result.returncode == 0:
# Parse JSON output
output_lines = result.stdout.split('\n')
for line in output_lines:
if line.startswith(device):
try:
data = json.loads(line.split(' => ', 1)[1])
return {
'success': True,
'device': device,
'facts': data.get('ansible_facts', {}),
'timestamp': datetime.now().isoformat()
}
except json.JSONDecodeError:
pass
return {
'success': False,
'error': f'Failed to get facts for {device}',
'timestamp': datetime.now().isoformat()
}
except Exception as e:
logger.exception(f"Failed to get device facts: {e}")
return {
'success': False,
'error': f'Failed to get device facts: {str(e)}',
'timestamp': datetime.now().isoformat()
}
def get_execution_logs(self, limit: int = 10) -> List[Dict[str, str]]:
"""Get recent execution logs"""
logs = []
try:
log_files = sorted(
Path(self.log_dir).glob('*.log'),
key=os.path.getmtime,
reverse=True
)[:limit]
for log_file in log_files:
logs.append({
'filename': log_file.name,
'timestamp': datetime.fromtimestamp(log_file.stat().st_mtime).isoformat(),
'size': log_file.stat().st_size
})
except Exception as e:
logger.error(f"Failed to get execution logs: {e}")
return logs
# Global instance
ansible_manager = None
def get_ansible_manager(ansible_dir: str = None) -> AnsibleManager:
"""Get or create global Ansible manager instance"""
global ansible_manager
if ansible_manager is None:
ansible_manager = AnsibleManager(ansible_dir)
return ansible_manager