chore: add current provisioning state before migration

This commit is contained in:
Jesús Pérez 2025-09-22 23:11:41 +01:00
parent a9703b4748
commit 50745b0f22
660 changed files with 88126 additions and 0 deletions

366
core/nulib/api/routes.nu Normal file
View file

@ -0,0 +1,366 @@
#!/usr/bin/env nu
# API Routes and handlers for Provisioning System
# Defines all REST API endpoints and their handlers
use ../lib_provisioning/utils/settings.nu *
use ../main_provisioning/query.nu *
# Route definitions for the API server
export def get_route_definitions []: nothing -> list {
[
{
method: "GET"
path: "/api/v1/health"
handler: "health_check"
description: "Health check endpoint"
parameters: []
}
{
method: "GET"
path: "/api/v1/query"
handler: "query_infrastructure"
description: "Query infrastructure state"
parameters: [
{ name: "target", type: "string", required: false, default: "servers", description: "Query target (servers, metrics, logs)" }
{ name: "infra", type: "string", required: false, description: "Infrastructure name" }
{ name: "provider", type: "string", required: false, description: "Provider filter" }
{ name: "find", type: "string", required: false, description: "Search filter" }
{ name: "format", type: "string", required: false, default: "json", description: "Output format" }
]
}
{
method: "POST"
path: "/api/v1/query"
handler: "complex_query"
description: "Execute complex queries with request body"
body_schema: {
type: "object"
properties: {
query_type: { type: "string", enum: ["infrastructure", "metrics", "logs", "ai"] }
target: { type: "string" }
filters: { type: "object" }
ai_query: { type: "string", description: "Natural language query" }
aggregations: { type: "array" }
}
}
}
{
method: "GET"
path: "/api/v1/metrics"
handler: "get_metrics"
description: "Retrieve system metrics"
parameters: [
{ name: "timerange", type: "string", default: "1h", description: "Time range (1m, 5m, 1h, 1d)" }
{ name: "metric_type", type: "string", description: "Metric type filter" }
{ name: "aggregation", type: "string", default: "avg", description: "Aggregation method" }
]
}
{
method: "GET"
path: "/api/v1/logs"
handler: "get_logs"
description: "Retrieve system logs"
parameters: [
{ name: "level", type: "string", default: "info", description: "Log level filter" }
{ name: "service", type: "string", description: "Service name filter" }
{ name: "since", type: "string", default: "1h", description: "Time since" }
{ name: "limit", type: "integer", default: 100, description: "Number of entries" }
]
}
{
method: "GET"
path: "/api/v1/dashboard"
handler: "get_dashboard_data"
description: "Dashboard data endpoint"
parameters: [
{ name: "view", type: "string", default: "overview", description: "Dashboard view" }
{ name: "refresh", type: "boolean", default: false, description: "Force refresh" }
]
}
{
method: "GET"
path: "/api/v1/servers"
handler: "list_servers"
description: "List all servers"
parameters: [
{ name: "status", type: "string", description: "Status filter" }
{ name: "provider", type: "string", description: "Provider filter" }
{ name: "infra", type: "string", description: "Infrastructure filter" }
]
}
{
method: "GET"
path: "/api/v1/servers/{id}"
handler: "get_server"
description: "Get specific server details"
path_params: [
{ name: "id", type: "string", required: true, description: "Server ID" }
]
}
{
method: "GET"
path: "/api/v1/servers/{id}/status"
handler: "get_server_status"
description: "Get server status and metrics"
path_params: [
{ name: "id", type: "string", required: true, description: "Server ID" }
]
}
{
method: "GET"
path: "/api/v1/servers/{id}/logs"
handler: "get_server_logs"
description: "Get server-specific logs"
path_params: [
{ name: "id", type: "string", required: true, description: "Server ID" }
]
}
{
method: "POST"
path: "/api/v1/servers"
handler: "create_server"
description: "Create new server"
body_schema: {
type: "object"
required: ["name", "provider"]
properties: {
name: { type: "string" }
provider: { type: "string" }
infra: { type: "string" }
instance_type: { type: "string" }
count: { type: "integer", default: 1 }
}
}
}
{
method: "DELETE"
path: "/api/v1/servers/{id}"
handler: "delete_server"
description: "Delete server"
path_params: [
{ name: "id", type: "string", required: true, description: "Server ID" }
]
}
{
method: "GET"
path: "/api/v1/ai/query"
handler: "ai_query"
description: "Natural language infrastructure queries"
parameters: [
{ name: "q", type: "string", required: true, description: "Natural language query" }
{ name: "context", type: "string", description: "Context for the query" }
]
}
{
method: "POST"
path: "/api/v1/ai/analyze"
handler: "ai_analyze"
description: "AI-powered infrastructure analysis"
body_schema: {
type: "object"
properties: {
analysis_type: { type: "string", enum: ["cost", "performance", "security", "optimization"] }
timerange: { type: "string", default: "24h" }
target: { type: "string" }
}
}
}
{
method: "GET"
path: "/api/v1/dataframes/query"
handler: "dataframe_query"
description: "Query infrastructure data using dataframes"
parameters: [
{ name: "source", type: "string", required: true, description: "Data source (logs, metrics, events)" }
{ name: "query", type: "string", required: true, description: "Polars/SQL-like query" }
{ name: "format", type: "string", default: "json", description: "Output format" }
]
}
{
method: "WebSocket"
path: "/ws/stream"
handler: "websocket_stream"
description: "Real-time updates via WebSocket"
parameters: [
{ name: "subscribe", type: "array", description: "Subscription topics" }
]
}
]
}
# Generate OpenAPI/Swagger specification
export def generate_api_spec []: nothing -> record {
let routes = get_route_definitions
{
openapi: "3.0.3"
info: {
title: "Provisioning System API"
description: "REST API for infrastructure provisioning and management"
version: "1.0.0"
contact: {
name: "Provisioning Team"
url: "https://github.com/provisioning-rs"
}
}
servers: [
{
url: "http://localhost:8080"
description: "Development server"
}
]
paths: ($routes | generate_paths)
components: {
schemas: (generate_schemas)
securitySchemes: {
BearerAuth: {
type: "http"
scheme: "bearer"
}
}
}
security: [
{ BearerAuth: [] }
]
}
}
def generate_paths []: list -> record {
let paths = {}
$in | each { |route|
let path_key = ($route.path | str replace -a "{id}" "{id}")
$paths | insert $path_key {
($route.method | str downcase): {
summary: $route.description
parameters: ($route.parameters? | default [] | each { |param|
{
name: $param.name
in: "query"
required: ($param.required? | default false)
schema: { type: $param.type }
description: $param.description?
}
})
responses: {
"200": {
description: "Successful response"
content: {
"application/json": {
schema: { type: "object" }
}
}
}
"400": {
description: "Bad request"
}
"500": {
description: "Internal server error"
}
}
}
}
} | last
}
def generate_schemas []: nothing -> record {
{
Error: {
type: "object"
properties: {
error: { type: "string" }
message: { type: "string" }
code: { type: "integer" }
}
}
HealthCheck: {
type: "object"
properties: {
status: { type: "string" }
service: { type: "string" }
version: { type: "string" }
timestamp: { type: "string" }
}
}
Server: {
type: "object"
properties: {
id: { type: "string" }
name: { type: "string" }
provider: { type: "string" }
status: { type: "string" }
ip_address: { type: "string" }
created_at: { type: "string" }
}
}
Metrics: {
type: "object"
properties: {
timestamp: { type: "string" }
cpu_usage: { type: "number" }
memory_usage: { type: "number" }
disk_usage: { type: "number" }
network_io: { type: "object" }
}
}
LogEntry: {
type: "object"
properties: {
timestamp: { type: "string" }
level: { type: "string" }
service: { type: "string" }
message: { type: "string" }
metadata: { type: "object" }
}
}
}
}
# Generate route documentation
export def generate_route_docs []: nothing -> str {
let routes = get_route_definitions
let header = "# Provisioning API Routes\n\nThis document describes all available API endpoints.\n\n"
let route_docs = ($routes | each { |route|
let params_doc = if ($route.parameters? | length) > 0 {
"\n**Parameters:**\n" + ($route.parameters | each { |p|
$"- `($p.name)` \\(($p.type)\\): ($p.description? | default 'No description')"
} | str join "\n")
} else { "" }
let body_doc = if ($route.body_schema? | is-not-empty) {
$"\n**Request Body:**\n```json\n($route.body_schema | to json)\n```"
} else { "" }
$"## ($route.method) ($route.path)\n\n($route.description)($params_doc)($body_doc)\n"
} | str join "\n")
$header + $route_docs
}
# Validate route configuration
export def validate_routes []: nothing -> record {
let routes = get_route_definitions
let validation_results = []
let path_conflicts = ($routes | group-by path | each { |path, group|
if ($group | length) > 1 {
let methods = ($group | get method)
let duplicate_methods = ($methods | uniq | length) != ($methods | length)
if $duplicate_methods {
{ path: $path, issue: "duplicate_methods", methods: $methods }
}
}
} | compact)
{
total_routes: ($routes | length)
unique_paths: ($routes | get path | uniq | length)
path_conflicts: $path_conflicts
validation_passed: ($path_conflicts | length) == 0
}
}

446
core/nulib/api/server.nu Normal file
View file

@ -0,0 +1,446 @@
#!/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"
}
}
}