#!/usr/bin/env nu # API Server for Provisioning System # Provides HTTP REST API endpoints for infrastructure queries and management use ../lib_provisioning/utils/settings.nu * use ../main_provisioning/query.nu * use ../lib_provisioning/ai/lib.nu * export def start_api_server [ --port: int = 8080 --host: string = "localhost" --enable-websocket --enable-cors --debug ]: nothing -> nothing { print $"šŸš€ Starting Provisioning API Server on ($host):($port)" if $debug { $env.PROVISIONING_API_DEBUG = "true" print "Debug mode enabled" } # Check if port is available let port_check = (check_port_available $port) if not $port_check { error make { msg: $"Port ($port) is already in use" help: "Try a different port with --port flag" } } # Setup server configuration let server_config = { host: $host port: $port enable_websocket: $enable_websocket enable_cors: $enable_cors debug: $debug routes: (get_api_routes) } print $"šŸ“” Server configuration: ($server_config | to json)" print "Available endpoints:" print " GET /api/v1/health - Health check" print " GET /api/v1/query - Infrastructure queries" print " POST /api/v1/query - Complex queries with body" print " GET /api/v1/metrics - System metrics" print " GET /api/v1/logs - System logs" print " GET /api/v1/dashboard - Dashboard data" if $enable_websocket { print " WS /ws/stream - WebSocket real-time updates" } # Start HTTP server start_http_server $server_config } def check_port_available [port: int]: nothing -> bool { # Try to bind to the port to check if it's available let result = (do -i { http listen $port --host "127.0.0.1" --timeout 1 | ignore }) match $result { null => false, # Port is busy _ => true # Port is available } } def get_api_routes []: nothing -> list { [ { method: "GET", path: "/api/v1/health", handler: "handle_health" } { method: "GET", path: "/api/v1/query", handler: "handle_query_get" } { method: "POST", path: "/api/v1/query", handler: "handle_query_post" } { method: "GET", path: "/api/v1/metrics", handler: "handle_metrics" } { method: "GET", path: "/api/v1/logs", handler: "handle_logs" } { method: "GET", path: "/api/v1/dashboard", handler: "handle_dashboard" } { method: "GET", path: "/api/v1/servers", handler: "handle_servers" } { method: "GET", path: "/api/v1/servers/{id}/status", handler: "handle_server_status" } ] } def start_http_server [config: record]: nothing -> nothing { print $"🌐 Starting HTTP server on ($config.host):($config.port)..." # Use a Python-based HTTP server for better compatibility let server_script = create_python_server $config # Save server script to temporary file let temp_server = $"/tmp/provisioning_api_server.py" $server_script | save --force $temp_server print $"šŸ“ Server script saved to: ($temp_server)" print "šŸŽÆ Starting server... (Press Ctrl+C to stop)" # Start the Python server python3 $temp_server } def create_python_server [config: record]: nothing -> str { let cors_headers = if $config.enable_cors { ''' self.send_header('Access-Control-Allow-Origin', '*') self.send_header('Access-Control-Allow-Methods', 'GET, POST, OPTIONS') self.send_header('Access-Control-Allow-Headers', 'Content-Type') ''' } else { "" } let websocket_import = if $config.enable_websocket { "import websockets" } else { "" } $"#!/usr/bin/env python3 import http.server import socketserver import json import subprocess import urllib.parse import os from pathlib import Path ($websocket_import) class ProvisioningAPIHandler(http.server.BaseHTTPRequestHandler): def do_OPTIONS(self): self.send_response(200) ($cors_headers) self.end_headers() def do_GET(self): self.handle_request('GET') def do_POST(self): self.handle_request('POST') def handle_request(self, method): try: path_parts = urllib.parse.urlparse(self.path) path = path_parts.path query_params = urllib.parse.parse_qs(path_parts.query) # Route handling if path == '/api/v1/health': self.handle_health() elif path == '/api/v1/query': if method == 'GET': self.handle_query_get(query_params) else: self.handle_query_post() elif path == '/api/v1/metrics': self.handle_metrics(query_params) elif path == '/api/v1/logs': self.handle_logs(query_params) elif path == '/api/v1/dashboard': self.handle_dashboard(query_params) elif path == '/api/v1/servers': self.handle_servers(query_params) elif path.startswith('/api/v1/servers/') and path.endswith('/status'): server_id = path.split('/')[-2] self.handle_server_status(server_id, query_params) else: self.send_error(404, 'Not Found') except Exception as e: self.send_error(500, f'Internal Server Error: {{str(e)}}') def handle_health(self): response = {{ 'status': 'healthy', 'service': 'provisioning-api', 'version': '1.0.0', 'timestamp': self.get_timestamp() }} self.send_json_response(response) def handle_query_get(self, params): # Convert query parameters to nushell command target = params.get('target', ['servers'])[0] infra = params.get('infra', [None])[0] find = params.get('find', [None])[0] cols = params.get('cols', [None])[0] out_format = params.get('format', ['json'])[0] cmd_args = ['nu', '-c', f'use ($env.PROVISIONING_PATH)/core/nulib/main_provisioning/query.nu; main query {{target}} --out {{out_format}}'] if infra: cmd_args[-1] = cmd_args[-1].replace('{{target}}', f'{{target}} --infra {{infra}}') result = self.run_provisioning_command(cmd_args) self.send_json_response(result) def handle_query_post(self): content_length = int(self.headers.get('Content-Length', 0)) if content_length > 0: post_data = self.rfile.read(content_length) try: query_data = json.loads(post_data.decode('utf-8')) # Process complex query result = self.process_complex_query(query_data) self.send_json_response(result) except json.JSONDecodeError: self.send_error(400, 'Invalid JSON') else: self.send_error(400, 'No data provided') def handle_metrics(self, params): timerange = params.get('timerange', ['1h'])[0] metric_type = params.get('type', ['all'])[0] # Mock metrics data - replace with actual metrics collection metrics = {{ 'cpu_usage': {{ 'current': 45.2, 'average': 38.7, 'max': 89.1, 'unit': 'percentage' }}, 'memory_usage': {{ 'current': 2.3, 'total': 8.0, 'unit': 'GB' }}, 'disk_usage': {{ 'used': 120.5, 'total': 500.0, 'unit': 'GB' }}, 'network_io': {{ 'in': 1024, 'out': 2048, 'unit': 'MB/s' }}, 'timestamp': self.get_timestamp(), 'timerange': timerange }} self.send_json_response(metrics) def handle_logs(self, params): level = params.get('level', ['info'])[0] limit = int(params.get('limit', ['100'])[0]) since = params.get('since', ['1h'])[0] # Mock log data - replace with actual log collection logs = {{ 'entries': [ {{ 'timestamp': '2024-01-16T10:30:00Z', 'level': 'info', 'service': 'provisioning-core', 'message': 'Server created successfully: web-01' }}, {{ 'timestamp': '2024-01-16T10:29:45Z', 'level': 'debug', 'service': 'aws-provider', 'message': 'EC2 instance launched: i-1234567890abcdef0' }} ], 'total': 2, 'filters': {{ 'level': level, 'limit': limit, 'since': since }} }} self.send_json_response(logs) def handle_dashboard(self, params): view = params.get('view', ['overview'])[0] dashboard_data = {{ 'overview': {{ 'total_servers': 25, 'active_servers': 23, 'failed_servers': 2, 'total_cost_monthly': 3250.75, 'cost_trend': '+5.2%', 'uptime': 99.7 }}, 'recent_activities': [ {{ 'type': 'deployment', 'message': 'Deployed application to production', 'timestamp': '2024-01-16T10:30:00Z', 'status': 'success' }}, {{ 'type': 'scaling', 'message': 'Auto-scaled web servers: 3 → 5', 'timestamp': '2024-01-16T10:25:00Z', 'status': 'success' }} ], 'alerts': [ {{ 'severity': 'warning', 'message': 'High CPU usage on web-01', 'timestamp': '2024-01-16T10:28:00Z' }} ] }} self.send_json_response(dashboard_data) def handle_servers(self, params): status_filter = params.get('status', [None])[0] provider = params.get('provider', [None])[0] # Use actual provisioning query command cmd_args = ['nu', '-c', f'use ($env.PROVISIONING_PATH)/core/nulib/main_provisioning/query.nu; main query servers --out json'] result = self.run_provisioning_command(cmd_args) self.send_json_response(result) def handle_server_status(self, server_id, params): # Mock server status - replace with actual server status check server_status = {{ 'server_id': server_id, 'status': 'running', 'uptime': '5d 12h 30m', 'cpu_usage': 34.2, 'memory_usage': 68.5, 'disk_usage': 45.1, 'network_in': 125.6, 'network_out': 89.3, 'last_check': self.get_timestamp() }} self.send_json_response(server_status) def run_provisioning_command(self, cmd_args): try: result = subprocess.run( cmd_args, capture_output=True, text=True, env={{**os.environ, 'PROVISIONING_OUT': 'json'}} ) if result.returncode == 0: try: return json.loads(result.stdout) except json.JSONDecodeError: return {{'output': result.stdout, 'raw': True}} else: return {{'error': result.stderr, 'returncode': result.returncode}} except Exception as e: return {{'error': str(e), 'type': 'execution_error'}} def process_complex_query(self, query_data): # Process complex queries with AI if available if 'ai_query' in query_data: # Use AI processing ai_result = self.process_ai_query(query_data['ai_query']) return ai_result else: # Standard complex query processing return {{'result': 'Complex query processed', 'data': query_data}} def process_ai_query(self, ai_query): try: cmd_args = [ 'nu', '-c', f'use ($env.PROVISIONING_PATH)/core/nulib/main_provisioning/query.nu; main query --ai-query \"{{ai_query}}\" --out json' ] result = self.run_provisioning_command(cmd_args) return result except Exception as e: return {{'error': f'AI query failed: {{str(e)}}'}} def send_json_response(self, data): self.send_response(200) self.send_header('Content-Type', 'application/json') ($cors_headers) self.end_headers() json_data = json.dumps(data, indent=2, ensure_ascii=False) self.wfile.write(json_data.encode('utf-8')) def get_timestamp(self): from datetime import datetime return datetime.utcnow().isoformat() + 'Z' def log_message(self, format, *args): if os.getenv('PROVISIONING_API_DEBUG') == 'true': super().log_message(format, *args) if __name__ == '__main__': HOST = '($config.host)' PORT = ($config.port) # Set environment variables os.environ['PROVISIONING_PATH'] = '($env.PROVISIONING_PATH | default "/usr/local/provisioning")' with socketserver.TCPServer((HOST, PORT), ProvisioningAPIHandler) as httpd: print(f'🌐 Provisioning API Server running on http://{{HOST}}:{{PORT}}') print('šŸ“‹ Available endpoints:') print(' GET /api/v1/health') print(' GET /api/v1/query') print(' POST /api/v1/query') print(' GET /api/v1/metrics') print(' GET /api/v1/logs') print(' GET /api/v1/dashboard') print(' GET /api/v1/servers') print(' GET /api/v1/servers/{{id}}/status') print('\\nšŸŽÆ Server ready! Press Ctrl+C to stop') try: httpd.serve_forever() except KeyboardInterrupt: print('\\nšŸ›‘ Server shutting down...') httpd.shutdown() print('āœ… Server stopped') " } # WebSocket server for real-time updates (if enabled) export def start_websocket_server [ --port: int = 8081 --host: string = "localhost" ]: nothing -> nothing { print $"šŸ”— Starting WebSocket server on ($host):($port) for real-time updates" print "This feature requires additional WebSocket implementation" print "Consider using a Rust-based WebSocket server for production use" } # Health check for the API server export def check_api_health [ --host: string = "localhost" --port: int = 8080 ]: nothing -> record { try { let response = http get $"http://($host):($port)/api/v1/health" { status: "healthy", api_server: true, response: $response } } catch { { status: "unhealthy", api_server: false, error: "Cannot connect to API server" } } }