package services import ( "context" "fmt" "sync" "go.uber.org/zap" "github.com/RyanCopley/skybridge/faas/internal/domain" "github.com/RyanCopley/skybridge/faas/internal/runtime" "github.com/RyanCopley/skybridge/faas/internal/runtime/docker" ) type runtimeService struct { backends map[string]runtime.RuntimeBackend mutex sync.RWMutex logger *zap.Logger config *RuntimeConfig } type RuntimeConfig struct { DefaultRuntime string `json:"default_runtime"` Backends map[string]map[string]interface{} `json:"backends"` } func NewRuntimeService(logger *zap.Logger, config *RuntimeConfig) RuntimeService { if config == nil { config = &RuntimeConfig{ DefaultRuntime: "docker", Backends: make(map[string]map[string]interface{}), } } service := &runtimeService{ backends: make(map[string]runtime.RuntimeBackend), logger: logger, config: config, } // Initialize default Docker backend if err := service.initializeDockerBackend(); err != nil { logger.Warn("Failed to initialize Docker backend", zap.Error(err)) } return service } func (s *runtimeService) initializeDockerBackend() error { // Use simple Docker backend for now dockerBackend, err := docker.NewSimpleDockerRuntime(s.logger) if err != nil { s.logger.Error("Failed to create Docker runtime", zap.Error(err)) return err } s.mutex.Lock() s.backends["docker"] = dockerBackend s.mutex.Unlock() s.logger.Info("Simple Docker runtime backend initialized") return nil } func (s *runtimeService) GetBackend(ctx context.Context, runtimeType string) (runtime.RuntimeBackend, error) { // Map domain runtime types to backend types backendType := s.mapRuntimeToBackend(runtimeType) s.mutex.RLock() backend, exists := s.backends[backendType] s.mutex.RUnlock() if !exists { return nil, fmt.Errorf("runtime backend '%s' not available", backendType) } // Check backend health if err := backend.HealthCheck(ctx); err != nil { s.logger.Warn("Runtime backend health check failed", zap.String("backend", backendType), zap.Error(err)) return nil, fmt.Errorf("runtime backend '%s' is not healthy: %w", backendType, err) } return backend, nil } func (s *runtimeService) ListSupportedRuntimes(ctx context.Context) ([]*domain.RuntimeInfo, error) { runtimes := []*domain.RuntimeInfo{ { Type: domain.RuntimeNodeJS18, Version: "18.x", Available: s.isRuntimeAvailable(ctx, "nodejs18"), DefaultImage: "node:18-alpine", Description: "Node.js 18.x runtime with Alpine Linux", }, { Type: domain.RuntimePython39, Version: "3.9.x", Available: s.isRuntimeAvailable(ctx, "python3.9"), DefaultImage: "python:3.9-alpine", Description: "Python 3.9.x runtime with Alpine Linux", }, { Type: domain.RuntimeGo120, Version: "1.20.x", Available: s.isRuntimeAvailable(ctx, "go1.20"), DefaultImage: "golang:1.20-alpine", Description: "Go 1.20.x runtime with Alpine Linux", }, { Type: domain.RuntimeCustom, Version: "custom", Available: s.isRuntimeAvailable(ctx, "custom"), DefaultImage: "alpine:latest", Description: "Custom runtime with user-defined image", }, } return runtimes, nil } func (s *runtimeService) HealthCheck(ctx context.Context, runtimeType string) error { backendType := s.mapRuntimeToBackend(runtimeType) s.mutex.RLock() backend, exists := s.backends[backendType] s.mutex.RUnlock() if !exists { return fmt.Errorf("runtime backend '%s' not available", backendType) } return backend.HealthCheck(ctx) } func (s *runtimeService) GetRuntimeInfo(ctx context.Context, runtimeType string) (*runtime.RuntimeInfo, error) { backendType := s.mapRuntimeToBackend(runtimeType) s.mutex.RLock() backend, exists := s.backends[backendType] s.mutex.RUnlock() if !exists { return nil, fmt.Errorf("runtime backend '%s' not available", backendType) } return backend.GetInfo(ctx) } func (s *runtimeService) ListContainers(ctx context.Context, runtimeType string) ([]runtime.ContainerInfo, error) { backendType := s.mapRuntimeToBackend(runtimeType) s.mutex.RLock() backend, exists := s.backends[backendType] s.mutex.RUnlock() if !exists { return nil, fmt.Errorf("runtime backend '%s' not available", backendType) } return backend.ListContainers(ctx) } func (s *runtimeService) mapRuntimeToBackend(runtimeType string) string { // For now, all runtimes use Docker backend // In the future, we could support different backends for different runtimes switch runtimeType { case string(domain.RuntimeNodeJS18): return "docker" case string(domain.RuntimePython39): return "docker" case string(domain.RuntimeGo120): return "docker" case string(domain.RuntimeCustom): return "docker" default: return s.config.DefaultRuntime } } func (s *runtimeService) isRuntimeAvailable(ctx context.Context, runtimeType string) bool { backendType := s.mapRuntimeToBackend(runtimeType) s.mutex.RLock() backend, exists := s.backends[backendType] s.mutex.RUnlock() if !exists { return false } if err := backend.HealthCheck(ctx); err != nil { return false } return true }