commti after qwen start
This commit is contained in:
@@ -0,0 +1,199 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
GLPI Agent Task: netconfig_backup
|
||||
Recupera configurazione da apparati di rete e invia a GLPI plugin netconfig
|
||||
Requisiti: netmiko, requests, pyyaml
|
||||
Install: pip3 install netmiko requests pyyaml
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import json
|
||||
import hashlib
|
||||
import logging
|
||||
import requests
|
||||
from datetime import datetime
|
||||
from typing import Optional, Dict, List
|
||||
from netmiko import ConnectHandler, NetmikoTimeoutException, NetmikoAuthenticationException
|
||||
import yaml
|
||||
|
||||
# Configurazione logging
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
||||
handlers=[
|
||||
logging.FileHandler('/var/log/glpi_agent_netconfig.log'),
|
||||
logging.StreamHandler(sys.stdout)
|
||||
]
|
||||
)
|
||||
logger = logging.getLogger('netconfig_backup')
|
||||
|
||||
# Configurazione da file YAML esterno (sicuro, non hardcodato)
|
||||
CONFIG_FILE = os.getenv('NETCONFIG_CONFIG', '/etc/glpi-agent/netconfig_devices.yaml')
|
||||
GLPI_URL = os.getenv('GLPI_URL', 'http://localhost/glpi')
|
||||
GLPI_TOKEN = os.getenv('GLPI_APP_TOKEN', '')
|
||||
AGENT_TOKEN = os.getenv('NETCONFIG_AGENT_TOKEN', 'change_me_in_production')
|
||||
TIMEOUT = int(os.getenv('NETCONFIG_TIMEOUT', '30'))
|
||||
RETRIES = int(os.getenv('NETCONFIG_RETRIES', '2'))
|
||||
|
||||
|
||||
def load_devices_config(path: str) -> List[Dict]:
|
||||
"""Carica configurazione dispositivi da YAML"""
|
||||
if not os.path.exists(path):
|
||||
logger.error(f"Config file not found: {path}")
|
||||
return []
|
||||
|
||||
try:
|
||||
with open(path, 'r') as f:
|
||||
data = yaml.safe_load(f)
|
||||
return data.get('devices', [])
|
||||
except Exception as e:
|
||||
logger.error(f"Error loading config: {e}")
|
||||
return []
|
||||
|
||||
|
||||
def get_device_connection_params(device: Dict) -> Dict:
|
||||
"""Prepara parametri di connessione per netmiko"""
|
||||
device_type_map = {
|
||||
'cisco_ios': 'cisco_ios',
|
||||
'cisco_nxos': 'cisco_nxos',
|
||||
'hp_comware': 'hp_comware',
|
||||
'juniper_junos': 'juniper_junos',
|
||||
'arista_eos': 'arista_eos',
|
||||
'paloalto_panos': 'paloalto_panos',
|
||||
}
|
||||
|
||||
return {
|
||||
'device_type': device_type_map.get(device['platform'], 'cisco_ios'),
|
||||
'host': device['ip'],
|
||||
'username': device.get('username', os.getenv('NETCONFIG_DEFAULT_USER')),
|
||||
'password': device.get('password', os.getenv('NETCONFIG_DEFAULT_PASS')),
|
||||
'secret': device.get('enable_password', os.getenv('NETCONFIG_ENABLE_PASS')),
|
||||
'port': device.get('port', 22),
|
||||
'timeout': TIMEOUT,
|
||||
'session_log': None,
|
||||
'global_delay_factor': device.get('delay_factor', 1),
|
||||
}
|
||||
|
||||
|
||||
def fetch_config(device: Dict) -> Optional[str]:
|
||||
"""Recupera configurazione via SSH con netmiko"""
|
||||
params = get_device_connection_params(device)
|
||||
|
||||
for attempt in range(RETRIES + 1):
|
||||
try:
|
||||
logger.info(f"Connecting to {device['name']} ({params['host']}) [attempt {attempt+1}]")
|
||||
connection = ConnectHandler(**params)
|
||||
|
||||
# Abilita modalità enable se necessaria
|
||||
if params.get('secret'):
|
||||
connection.enable()
|
||||
|
||||
# Comando di show config (personalizzabile per piattaforma)
|
||||
command = device.get('command', 'show running-config')
|
||||
config = connection.send_command(command, expect_string=r'#|\$', max_loops=150)
|
||||
|
||||
connection.disconnect()
|
||||
|
||||
# Pulizia output da caratteri di controllo
|
||||
config = config.strip()
|
||||
config = '\n'.join(line.rstrip() for line in config.splitlines())
|
||||
|
||||
logger.info(f"Config fetched successfully for {device['name']}")
|
||||
return config
|
||||
|
||||
except NetmikoAuthenticationException as e:
|
||||
logger.error(f"Auth failed for {device['name']}: {e}")
|
||||
break # Non ritentare se auth fallisce
|
||||
except NetmikoTimeoutException as e:
|
||||
logger.warning(f"Timeout for {device['name']}: {e}")
|
||||
except Exception as e:
|
||||
logger.error(f"Unexpected error for {device['name']}: {e}")
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def send_to_glpi(device_id: int, config: str) -> bool:
|
||||
"""Invia configurazione a GLPI plugin endpoint"""
|
||||
url = f"{GLPI_URL}/plugins/netconfig/ajax/agent_receive.php"
|
||||
|
||||
payload = {
|
||||
'device_id': device_id,
|
||||
'config': config,
|
||||
'token': AGENT_TOKEN,
|
||||
'timestamp': datetime.utcnow().isoformat(),
|
||||
'agent_version': '1.0.0'
|
||||
}
|
||||
|
||||
headers = {
|
||||
'Content-Type': 'application/json',
|
||||
'App-Token': GLPI_TOKEN, # Opzionale: autenticazione API GLPI
|
||||
}
|
||||
|
||||
try:
|
||||
response = requests.post(
|
||||
url,
|
||||
json=payload,
|
||||
headers=headers,
|
||||
timeout=30,
|
||||
verify=os.getenv('SSL_VERIFY', 'true').lower() == 'true'
|
||||
)
|
||||
|
||||
if response.status_code == 200:
|
||||
result = response.json()
|
||||
logger.info(f"GLPI response: {result.get('message', 'OK')}")
|
||||
return result.get('status') == 'ok'
|
||||
else:
|
||||
logger.error(f"GLPI HTTP {response.status_code}: {response.text}")
|
||||
return False
|
||||
|
||||
except requests.RequestException as e:
|
||||
logger.error(f"Request to GLPI failed: {e}")
|
||||
return False
|
||||
|
||||
|
||||
def calculate_hash(config: str) -> str:
|
||||
"""Calcola SHA256 della configurazione"""
|
||||
return hashlib.sha256(config.encode('utf-8')).hexdigest()
|
||||
|
||||
|
||||
def run_task():
|
||||
"""Entry point della task"""
|
||||
logger.info("=== NetConfig Backup Task Started ===")
|
||||
|
||||
devices = load_devices_config(CONFIG_FILE)
|
||||
if not devices:
|
||||
logger.warning("No devices configured, exiting")
|
||||
return
|
||||
|
||||
success_count = 0
|
||||
for device in devices:
|
||||
try:
|
||||
device_name = device.get('name', 'unknown')
|
||||
device_id = device.get('glpi_id') # ID in glpi_networkdevices
|
||||
|
||||
if not device_id:
|
||||
logger.warning(f"Skipping {device_name}: missing glpi_id")
|
||||
continue
|
||||
|
||||
config = fetch_config(device)
|
||||
if not config:
|
||||
logger.warning(f"Failed to fetch config for {device_name}")
|
||||
continue
|
||||
|
||||
# Invia a GLPI solo se diverso dall'ultimo (controllo lato server, ma ottimizzazione client)
|
||||
if send_to_glpi(device_id, config):
|
||||
success_count += 1
|
||||
logger.info(f"✓ {device_name} - Config saved")
|
||||
else:
|
||||
logger.error(f"✗ {device_name} - Failed to save to GLPI")
|
||||
|
||||
except Exception as e:
|
||||
logger.exception(f"Unhandled error for device {device.get('name')}")
|
||||
|
||||
logger.info(f"=== Task Completed: {success_count}/{len(devices)} devices processed ===")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
run_task()
|
||||
Reference in New Issue
Block a user