Skip to main content
Glama
template_advanced.py18.2 kB
"""Handler for advanced template management operations.""" from datetime import datetime from typing import Any import json import difflib from ludus_mcp.core.client import LudusAPIClient from ludus_mcp.utils.logging import get_logger logger = get_logger(__name__) class TemplateAdvancedHandler: """Handler for advanced template management.""" def __init__(self, client: LudusAPIClient) -> None: """Initialize the template advanced handler.""" self.client = client async def template_diff( self, template1: str, template2: str, user_id: str | None = None ) -> dict[str, Any]: """ Compare two template configurations. Args: template1: First template name or ID template2: Second template name or ID user_id: Optional user ID (admin only) Returns: Dictionary with comparison results """ try: # Get both template configurations templates = await self.client.list_templates(user_id) t1 = next((t for t in templates if t.get("name") == template1 or t.get("id") == template1), None) t2 = next((t for t in templates if t.get("name") == template2 or t.get("id") == template2), None) if not t1: return {"status": "error", "error": f"Template not found: {template1}"} if not t2: return {"status": "error", "error": f"Template not found: {template2}"} # Convert to JSON strings for comparison t1_json = json.dumps(t1, indent=2, sort_keys=True) t2_json = json.dumps(t2, indent=2, sort_keys=True) # Generate unified diff diff_lines = list(difflib.unified_diff( t1_json.splitlines(keepends=True), t2_json.splitlines(keepends=True), fromfile=template1, tofile=template2, lineterm='' )) # Analyze differences differences = self._analyze_template_differences(t1, t2) result = { "status": "success", "timestamp": datetime.now().isoformat(), "template1": { "name": t1.get("name"), "id": t1.get("id"), "type": t1.get("type", "unknown") }, "template2": { "name": t2.get("name"), "id": t2.get("id"), "type": t2.get("type", "unknown") }, "differences": differences, "unified_diff": ''.join(diff_lines), "identical": len(differences) == 0 } logger.info(f"Template diff: {template1} vs {template2}, {len(differences)} differences") return result except Exception as e: logger.error(f"Error comparing templates: {e}") return { "status": "error", "error": str(e) } def _analyze_template_differences(self, t1: dict, t2: dict) -> list[dict]: """Analyze specific differences between templates.""" differences = [] # Compare common fields fields_to_compare = ["name", "type", "version", "description", "os", "architecture"] for field in fields_to_compare: v1 = t1.get(field) v2 = t2.get(field) if v1 != v2: differences.append({ "field": field, "template1_value": v1, "template2_value": v2, "type": "field_difference" }) # Check for unique keys keys1 = set(t1.keys()) keys2 = set(t2.keys()) only_in_t1 = keys1 - keys2 only_in_t2 = keys2 - keys1 if only_in_t1: differences.append({ "type": "unique_keys", "template": "template1", "keys": list(only_in_t1) }) if only_in_t2: differences.append({ "type": "unique_keys", "template": "template2", "keys": list(only_in_t2) }) return differences async def validate_template( self, template_config: dict, user_id: str | None = None ) -> dict[str, Any]: """ Validate template configuration before building. Args: template_config: Template configuration to validate user_id: Optional user ID (admin only) Returns: Dictionary with validation results """ try: errors = [] warnings = [] suggestions = [] # Required fields validation required_fields = ["name", "type"] for field in required_fields: if field not in template_config: errors.append({ "severity": "error", "field": field, "message": f"Required field '{field}' is missing" }) # Name validation if "name" in template_config: name = template_config["name"] if not isinstance(name, str) or len(name) < 3: errors.append({ "severity": "error", "field": "name", "message": "Template name must be at least 3 characters" }) if " " in name: warnings.append({ "severity": "warning", "field": "name", "message": "Template name contains spaces, consider using hyphens" }) # Type validation valid_types = ["vm", "container", "custom"] if "type" in template_config: if template_config["type"] not in valid_types: warnings.append({ "severity": "warning", "field": "type", "message": f"Unexpected template type. Common types: {valid_types}" }) # Resource validation if "resources" in template_config: resources = template_config["resources"] if isinstance(resources, dict): # Memory check if "memory" in resources: memory = resources["memory"] if memory < 512: warnings.append({ "severity": "warning", "field": "resources.memory", "message": "Memory less than 512MB may cause issues" }) if memory > 32768: warnings.append({ "severity": "warning", "field": "resources.memory", "message": "Very high memory allocation (>32GB)" }) # CPU check if "cpus" in resources: cpus = resources["cpus"] if cpus < 1: errors.append({ "severity": "error", "field": "resources.cpus", "message": "At least 1 CPU required" }) if cpus > 16: warnings.append({ "severity": "warning", "field": "resources.cpus", "message": "Very high CPU allocation (>16 cores)" }) # Suggestions if "description" not in template_config: suggestions.append("Add a description field to document the template purpose") if "version" not in template_config: suggestions.append("Add a version field for better template tracking") # Overall validation result is_valid = len(errors) == 0 validation_level = "valid" if is_valid else "invalid" if is_valid and warnings: validation_level = "valid_with_warnings" result = { "status": "success", "timestamp": datetime.now().isoformat(), "validation_result": validation_level, "is_valid": is_valid, "errors": errors, "warnings": warnings, "suggestions": suggestions, "summary": { "error_count": len(errors), "warning_count": len(warnings), "suggestion_count": len(suggestions) } } logger.info(f"Template validation: {validation_level}, {len(errors)} errors, {len(warnings)} warnings") return result except Exception as e: logger.error(f"Error validating template: {e}") return { "status": "error", "error": str(e) } async def get_template_dependencies( self, template_name: str, user_id: str | None = None ) -> dict[str, Any]: """ List required roles and collections for a template. Args: template_name: Template name or ID user_id: Optional user ID (admin only) Returns: Dictionary with template dependencies """ try: templates = await self.client.list_templates(user_id) template = next((t for t in templates if t.get("name") == template_name or t.get("id") == template_name), None) if not template: return { "status": "error", "error": f"Template not found: {template_name}" } # Extract dependencies from template configuration dependencies = { "ansible_roles": [], "ansible_collections": [], "system_packages": [], "custom_scripts": [] } # Parse template for common Ludus roles template_str = json.dumps(template).lower() # Common Ludus roles common_roles = [ "ludus_ad", "ludus_dc", "ludus_domain_user", "ludus_windows", "ludus_linux", "badsecrets", "geerlingguy.docker", "geerlingguy.postgresql" ] for role in common_roles: if role.lower() in template_str: dependencies["ansible_roles"].append(role) # Common collections if "community" in template_str: dependencies["ansible_collections"].append("community.general") if "ansible.windows" in template_str: dependencies["ansible_collections"].append("ansible.windows") result = { "status": "success", "timestamp": datetime.now().isoformat(), "template": { "name": template.get("name"), "id": template.get("id"), "type": template.get("type") }, "dependencies": dependencies, "installation_commands": self._generate_installation_commands(dependencies), "note": "This is an estimated dependency list based on template analysis" } logger.info(f"Template dependencies for {template_name}: {len(dependencies['ansible_roles'])} roles") return result except Exception as e: logger.error(f"Error getting template dependencies: {e}") return { "status": "error", "error": str(e) } def _generate_installation_commands(self, dependencies: dict) -> dict[str, list[str]]: """Generate installation commands for dependencies.""" commands = { "ansible_galaxy": [], "package_manager": [] } # Ansible roles for role in dependencies.get("ansible_roles", []): commands["ansible_galaxy"].append(f"ansible-galaxy role install {role}") # Ansible collections for collection in dependencies.get("ansible_collections", []): commands["ansible_galaxy"].append(f"ansible-galaxy collection install {collection}") # System packages for package in dependencies.get("system_packages", []): commands["package_manager"].append(f"apt install {package} # or yum/dnf") return commands async def optimize_template( self, template_name: str, user_id: str | None = None ) -> dict[str, Any]: """ Suggest optimizations for template configuration. Args: template_name: Template name or ID user_id: Optional user ID (admin only) Returns: Dictionary with optimization suggestions """ try: templates = await self.client.list_templates(user_id) template = next((t for t in templates if t.get("name") == template_name or t.get("id") == template_name), None) if not template: return { "status": "error", "error": f"Template not found: {template_name}" } optimizations = [] potential_savings = { "memory_mb": 0, "cpus": 0, "disk_gb": 0 } # Analyze template configuration template_type = template.get("type", "unknown") # Memory optimization if "memory" in template: memory = template["memory"] if memory > 8192: optimizations.append({ "category": "memory", "priority": "medium", "suggestion": f"Memory allocation of {memory}MB is quite high. Consider if full amount is needed.", "current_value": memory, "suggested_value": 8192, "potential_savings_mb": memory - 8192 }) potential_savings["memory_mb"] = memory - 8192 # CPU optimization if "cpus" in template: cpus = template["cpus"] if cpus > 4: optimizations.append({ "category": "cpu", "priority": "medium", "suggestion": f"CPU allocation of {cpus} cores is high. Most VMs work well with 2-4 cores.", "current_value": cpus, "suggested_value": 4, "potential_savings_cpus": cpus - 4 }) potential_savings["cpus"] = cpus - 4 # Disk optimization if "disk" in template: disk = template["disk"] if disk > 100: optimizations.append({ "category": "disk", "priority": "low", "suggestion": f"Disk allocation of {disk}GB is large. Consider if full space is needed.", "current_value": disk, "suggested_value": 80, "potential_savings_gb": disk - 80 }) potential_savings["disk_gb"] = disk - 80 # Performance optimizations optimizations.append({ "category": "performance", "priority": "high", "suggestion": "Enable disk caching for better I/O performance", "implementation": "Add 'cache_mode: writeback' to disk configuration" }) optimizations.append({ "category": "performance", "priority": "medium", "suggestion": "Use VirtIO drivers for better network performance", "implementation": "Ensure VirtIO drivers are installed in the VM" }) # Security optimizations optimizations.append({ "category": "security", "priority": "high", "suggestion": "Ensure template uses latest OS version", "implementation": "Update base image to latest stable release" }) result = { "status": "success", "timestamp": datetime.now().isoformat(), "template": { "name": template.get("name"), "id": template.get("id"), "type": template_type }, "optimizations": optimizations, "potential_savings": potential_savings, "summary": { "total_suggestions": len(optimizations), "high_priority": sum(1 for o in optimizations if o.get("priority") == "high"), "medium_priority": sum(1 for o in optimizations if o.get("priority") == "medium"), "low_priority": sum(1 for o in optimizations if o.get("priority") == "low") } } logger.info(f"Template optimization for {template_name}: {len(optimizations)} suggestions") return result except Exception as e: logger.error(f"Error optimizing template: {e}") return { "status": "error", "error": str(e) }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/tjnull/Ludus-FastMCP'

If you have feedback or need assistance with the MCP directory API, please join our Discord server