This commit is contained in:
2025-08-30 21:17:23 -04:00
parent f72c05bfd8
commit 2778cbc512
46 changed files with 11717 additions and 0 deletions

View File

@ -0,0 +1,75 @@
package services
import (
"context"
"fmt"
"go.uber.org/zap"
"github.com/RyanCopley/skybridge/faas/internal/domain"
)
type authService struct {
logger *zap.Logger
}
func NewAuthService(logger *zap.Logger) AuthService {
return &authService{
logger: logger,
}
}
// Mock implementation for now - this should integrate with the KMS auth system
func (s *authService) GetAuthContext(ctx context.Context) (*domain.AuthContext, error) {
// For now, return a mock auth context
// In a real implementation, this would extract auth info from the request context
// that was set by middleware that validates tokens with the KMS service
return &domain.AuthContext{
UserID: "admin@example.com",
AppID: "faas-service",
Permissions: []string{"faas.read", "faas.write", "faas.execute", "faas.deploy", "faas.delete"},
Claims: map[string]string{
"user_type": "admin",
},
}, nil
}
func (s *authService) HasPermission(ctx context.Context, permission string) bool {
authCtx, err := s.GetAuthContext(ctx)
if err != nil {
s.logger.Warn("Failed to get auth context for permission check", zap.Error(err))
return false
}
// Check for exact permission match
for _, perm := range authCtx.Permissions {
if perm == permission {
return true
}
// Check for wildcard permissions (e.g., "faas.*" grants all faas permissions)
if len(perm) > 2 && perm[len(perm)-1] == '*' {
prefix := perm[:len(perm)-1]
if len(permission) >= len(prefix) && permission[:len(prefix)] == prefix {
return true
}
}
}
s.logger.Debug("Permission denied",
zap.String("user_id", authCtx.UserID),
zap.String("permission", permission),
zap.Strings("user_permissions", authCtx.Permissions))
return false
}
func (s *authService) ValidatePermissions(ctx context.Context, permissions []string) error {
for _, permission := range permissions {
if !s.HasPermission(ctx, permission) {
return fmt.Errorf("insufficient permission: %s", permission)
}
}
return nil
}

View File

@ -0,0 +1,309 @@
package services
import (
"context"
"fmt"
"time"
"go.uber.org/zap"
"github.com/RyanCopley/skybridge/faas/internal/domain"
"github.com/RyanCopley/skybridge/faas/internal/repository"
"github.com/google/uuid"
)
type executionService struct {
executionRepo repository.ExecutionRepository
functionRepo repository.FunctionRepository
runtimeService RuntimeService
logger *zap.Logger
}
func NewExecutionService(
executionRepo repository.ExecutionRepository,
functionRepo repository.FunctionRepository,
runtimeService RuntimeService,
logger *zap.Logger,
) ExecutionService {
return &executionService{
executionRepo: executionRepo,
functionRepo: functionRepo,
runtimeService: runtimeService,
logger: logger,
}
}
func (s *executionService) Execute(ctx context.Context, req *domain.ExecuteFunctionRequest, userID string) (*domain.ExecuteFunctionResponse, error) {
s.logger.Info("Executing function",
zap.String("function_id", req.FunctionID.String()),
zap.String("user_id", userID),
zap.Bool("async", req.Async))
// Get function definition
function, err := s.functionRepo.GetByID(ctx, req.FunctionID)
if err != nil {
return nil, fmt.Errorf("function not found: %w", err)
}
// Create execution record
execution := &domain.FunctionExecution{
ID: uuid.New(),
FunctionID: req.FunctionID,
Status: domain.StatusPending,
Input: req.Input,
ExecutorID: userID,
CreatedAt: time.Now(),
}
// Store execution
createdExecution, err := s.executionRepo.Create(ctx, execution)
if err != nil {
s.logger.Error("Failed to create execution record",
zap.String("function_id", req.FunctionID.String()),
zap.Error(err))
return nil, fmt.Errorf("failed to create execution record: %w", err)
}
if req.Async {
// Start async execution
go s.executeAsync(context.Background(), createdExecution, function)
return &domain.ExecuteFunctionResponse{
ExecutionID: createdExecution.ID,
Status: domain.StatusPending,
}, nil
} else {
// Execute synchronously
return s.executeSync(ctx, createdExecution, function)
}
}
func (s *executionService) executeSync(ctx context.Context, execution *domain.FunctionExecution, function *domain.FunctionDefinition) (*domain.ExecuteFunctionResponse, error) {
// Update status to running
execution.Status = domain.StatusRunning
execution.StartedAt = &[]time.Time{time.Now()}[0]
if _, err := s.executionRepo.Update(ctx, execution.ID, execution); err != nil {
s.logger.Warn("Failed to update execution status to running", zap.Error(err))
}
// Get runtime backend
backend, err := s.runtimeService.GetBackend(ctx, string(function.Runtime))
if err != nil {
execution.Status = domain.StatusFailed
execution.Error = fmt.Sprintf("failed to get runtime backend: %v", err)
s.updateExecutionComplete(ctx, execution)
return &domain.ExecuteFunctionResponse{
ExecutionID: execution.ID,
Status: domain.StatusFailed,
Error: execution.Error,
}, nil
}
// Execute function
result, err := backend.Execute(ctx, function, execution.Input)
if err != nil {
execution.Status = domain.StatusFailed
execution.Error = fmt.Sprintf("execution failed: %v", err)
s.updateExecutionComplete(ctx, execution)
return &domain.ExecuteFunctionResponse{
ExecutionID: execution.ID,
Status: domain.StatusFailed,
Error: execution.Error,
}, nil
}
// Update execution with results
execution.Status = domain.StatusCompleted
execution.Output = result.Output
execution.Error = result.Error
execution.Duration = result.Duration
execution.MemoryUsed = result.MemoryUsed
s.updateExecutionComplete(ctx, execution)
if result.Error != "" {
execution.Status = domain.StatusFailed
}
return &domain.ExecuteFunctionResponse{
ExecutionID: execution.ID,
Status: execution.Status,
Output: execution.Output,
Error: execution.Error,
Duration: execution.Duration,
MemoryUsed: execution.MemoryUsed,
}, nil
}
func (s *executionService) executeAsync(ctx context.Context, execution *domain.FunctionExecution, function *domain.FunctionDefinition) {
// Update status to running
execution.Status = domain.StatusRunning
execution.StartedAt = &[]time.Time{time.Now()}[0]
if _, err := s.executionRepo.Update(ctx, execution.ID, execution); err != nil {
s.logger.Warn("Failed to update execution status to running", zap.Error(err))
}
// Get runtime backend
backend, err := s.runtimeService.GetBackend(ctx, string(function.Runtime))
if err != nil {
s.logger.Error("Failed to get runtime backend for async execution",
zap.String("execution_id", execution.ID.String()),
zap.Error(err))
execution.Status = domain.StatusFailed
execution.Error = fmt.Sprintf("failed to get runtime backend: %v", err)
s.updateExecutionComplete(ctx, execution)
return
}
// Execute function
result, err := backend.Execute(ctx, function, execution.Input)
if err != nil {
s.logger.Error("Async function execution failed",
zap.String("execution_id", execution.ID.String()),
zap.Error(err))
execution.Status = domain.StatusFailed
execution.Error = fmt.Sprintf("execution failed: %v", err)
s.updateExecutionComplete(ctx, execution)
return
}
// Update execution with results
execution.Status = domain.StatusCompleted
execution.Output = result.Output
execution.Error = result.Error
execution.Duration = result.Duration
execution.MemoryUsed = result.MemoryUsed
if result.Error != "" {
execution.Status = domain.StatusFailed
}
s.updateExecutionComplete(ctx, execution)
s.logger.Info("Async function execution completed",
zap.String("execution_id", execution.ID.String()),
zap.String("status", string(execution.Status)),
zap.Duration("duration", execution.Duration))
}
func (s *executionService) updateExecutionComplete(ctx context.Context, execution *domain.FunctionExecution) {
execution.CompletedAt = &[]time.Time{time.Now()}[0]
if _, err := s.executionRepo.Update(ctx, execution.ID, execution); err != nil {
s.logger.Error("Failed to update execution completion",
zap.String("execution_id", execution.ID.String()),
zap.Error(err))
}
}
func (s *executionService) GetByID(ctx context.Context, id uuid.UUID) (*domain.FunctionExecution, error) {
execution, err := s.executionRepo.GetByID(ctx, id)
if err != nil {
return nil, fmt.Errorf("execution not found: %w", err)
}
return execution, nil
}
func (s *executionService) List(ctx context.Context, functionID *uuid.UUID, limit, offset int) ([]*domain.FunctionExecution, error) {
if limit <= 0 {
limit = 50 // Default limit
}
if limit > 100 {
limit = 100 // Max limit
}
return s.executionRepo.List(ctx, functionID, limit, offset)
}
func (s *executionService) GetByFunctionID(ctx context.Context, functionID uuid.UUID, limit, offset int) ([]*domain.FunctionExecution, error) {
if limit <= 0 {
limit = 50 // Default limit
}
if limit > 100 {
limit = 100 // Max limit
}
return s.executionRepo.GetByFunctionID(ctx, functionID, limit, offset)
}
func (s *executionService) Cancel(ctx context.Context, id uuid.UUID, userID string) error {
s.logger.Info("Canceling execution",
zap.String("execution_id", id.String()),
zap.String("user_id", userID))
// Get execution
execution, err := s.executionRepo.GetByID(ctx, id)
if err != nil {
return fmt.Errorf("execution not found: %w", err)
}
// Check if execution is still running
if execution.Status != domain.StatusRunning && execution.Status != domain.StatusPending {
return fmt.Errorf("execution is not running (status: %s)", execution.Status)
}
// Get function to determine runtime
function, err := s.functionRepo.GetByID(ctx, execution.FunctionID)
if err != nil {
return fmt.Errorf("function not found: %w", err)
}
// Stop execution in runtime
backend, err := s.runtimeService.GetBackend(ctx, string(function.Runtime))
if err != nil {
return fmt.Errorf("failed to get runtime backend: %w", err)
}
if err := backend.StopExecution(ctx, id); err != nil {
s.logger.Warn("Failed to stop execution in runtime",
zap.String("execution_id", id.String()),
zap.Error(err))
}
// Update execution status
execution.Status = domain.StatusCanceled
execution.Error = "execution canceled by user"
execution.CompletedAt = &[]time.Time{time.Now()}[0]
if _, err := s.executionRepo.Update(ctx, execution.ID, execution); err != nil {
return fmt.Errorf("failed to update execution status: %w", err)
}
s.logger.Info("Execution canceled successfully",
zap.String("execution_id", id.String()))
return nil
}
func (s *executionService) GetLogs(ctx context.Context, id uuid.UUID) ([]string, error) {
// Get execution
execution, err := s.executionRepo.GetByID(ctx, id)
if err != nil {
return nil, fmt.Errorf("execution not found: %w", err)
}
// Get function to determine runtime
function, err := s.functionRepo.GetByID(ctx, execution.FunctionID)
if err != nil {
return nil, fmt.Errorf("function not found: %w", err)
}
// Get runtime backend
backend, err := s.runtimeService.GetBackend(ctx, string(function.Runtime))
if err != nil {
return nil, fmt.Errorf("failed to get runtime backend: %w", err)
}
// Get logs from runtime
logs, err := backend.GetLogs(ctx, id)
if err != nil {
return nil, fmt.Errorf("failed to get logs: %w", err)
}
return logs, nil
}
func (s *executionService) GetRunningExecutions(ctx context.Context) ([]*domain.FunctionExecution, error) {
return s.executionRepo.GetRunningExecutions(ctx)
}

View File

@ -0,0 +1,253 @@
package services
import (
"context"
"fmt"
"time"
"go.uber.org/zap"
"github.com/RyanCopley/skybridge/faas/internal/domain"
"github.com/RyanCopley/skybridge/faas/internal/repository"
"github.com/google/uuid"
)
type functionService struct {
functionRepo repository.FunctionRepository
runtimeService RuntimeService
logger *zap.Logger
}
func NewFunctionService(functionRepo repository.FunctionRepository, runtimeService RuntimeService, logger *zap.Logger) FunctionService {
return &functionService{
functionRepo: functionRepo,
runtimeService: runtimeService,
logger: logger,
}
}
func (s *functionService) Create(ctx context.Context, req *domain.CreateFunctionRequest, userID string) (*domain.FunctionDefinition, error) {
s.logger.Info("Creating new function",
zap.String("name", req.Name),
zap.String("app_id", req.AppID),
zap.String("user_id", userID))
// Check if function with same name exists
_, err := s.functionRepo.GetByName(ctx, req.AppID, req.Name)
if err == nil {
return nil, fmt.Errorf("function with name '%s' already exists in app '%s'", req.Name, req.AppID)
}
// Validate runtime
if !s.isValidRuntime(string(req.Runtime)) {
return nil, fmt.Errorf("unsupported runtime: %s", req.Runtime)
}
// Create function definition
function := &domain.FunctionDefinition{
ID: uuid.New(),
Name: req.Name,
AppID: req.AppID,
Runtime: req.Runtime,
Image: req.Image,
Handler: req.Handler,
Code: req.Code,
Environment: req.Environment,
Timeout: req.Timeout,
Memory: req.Memory,
Owner: req.Owner,
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
}
// Validate timeout and memory limits
if function.Timeout.Duration < time.Second {
return nil, fmt.Errorf("timeout must be at least 1 second")
}
if function.Timeout.Duration > 15*time.Minute {
return nil, fmt.Errorf("timeout cannot exceed 15 minutes")
}
if function.Memory < 64 || function.Memory > 3008 {
return nil, fmt.Errorf("memory must be between 64 and 3008 MB")
}
// Store function
created, err := s.functionRepo.Create(ctx, function)
if err != nil {
s.logger.Error("Failed to create function",
zap.String("name", req.Name),
zap.Error(err))
return nil, fmt.Errorf("failed to create function: %w", err)
}
s.logger.Info("Function created successfully",
zap.String("function_id", created.ID.String()),
zap.String("name", created.Name))
return created, nil
}
func (s *functionService) GetByID(ctx context.Context, id uuid.UUID) (*domain.FunctionDefinition, error) {
function, err := s.functionRepo.GetByID(ctx, id)
if err != nil {
return nil, fmt.Errorf("function not found: %w", err)
}
return function, nil
}
func (s *functionService) GetByName(ctx context.Context, appID, name string) (*domain.FunctionDefinition, error) {
function, err := s.functionRepo.GetByName(ctx, appID, name)
if err != nil {
return nil, fmt.Errorf("function not found: %w", err)
}
return function, nil
}
func (s *functionService) Update(ctx context.Context, id uuid.UUID, req *domain.UpdateFunctionRequest, userID string) (*domain.FunctionDefinition, error) {
s.logger.Info("Updating function",
zap.String("function_id", id.String()),
zap.String("user_id", userID))
// Get existing function
_, err := s.functionRepo.GetByID(ctx, id)
if err != nil {
return nil, fmt.Errorf("function not found: %w", err)
}
// Validate runtime if being updated
if req.Runtime != nil && !s.isValidRuntime(string(*req.Runtime)) {
return nil, fmt.Errorf("unsupported runtime: %s", *req.Runtime)
}
// Validate timeout and memory if being updated
if req.Timeout != nil {
if req.Timeout.Duration < time.Second {
return nil, fmt.Errorf("timeout must be at least 1 second")
}
if req.Timeout.Duration > 15*time.Minute {
return nil, fmt.Errorf("timeout cannot exceed 15 minutes")
}
}
if req.Memory != nil && (*req.Memory < 64 || *req.Memory > 3008) {
return nil, fmt.Errorf("memory must be between 64 and 3008 MB")
}
// Update function
updated, err := s.functionRepo.Update(ctx, id, req)
if err != nil {
s.logger.Error("Failed to update function",
zap.String("function_id", id.String()),
zap.Error(err))
return nil, fmt.Errorf("failed to update function: %w", err)
}
s.logger.Info("Function updated successfully",
zap.String("function_id", id.String()))
return updated, nil
}
func (s *functionService) Delete(ctx context.Context, id uuid.UUID, userID string) error {
s.logger.Info("Deleting function",
zap.String("function_id", id.String()),
zap.String("user_id", userID))
// Get function to determine runtime
function, err := s.functionRepo.GetByID(ctx, id)
if err != nil {
return fmt.Errorf("function not found: %w", err)
}
// Clean up runtime resources
backend, err := s.runtimeService.GetBackend(ctx, string(function.Runtime))
if err != nil {
s.logger.Warn("Failed to get runtime backend for cleanup", zap.Error(err))
} else {
if err := backend.Remove(ctx, id); err != nil {
s.logger.Warn("Failed to remove runtime resources",
zap.String("function_id", id.String()),
zap.Error(err))
}
}
// Delete function
if err := s.functionRepo.Delete(ctx, id); err != nil {
s.logger.Error("Failed to delete function",
zap.String("function_id", id.String()),
zap.Error(err))
return fmt.Errorf("failed to delete function: %w", err)
}
s.logger.Info("Function deleted successfully",
zap.String("function_id", id.String()))
return nil
}
func (s *functionService) List(ctx context.Context, appID string, limit, offset int) ([]*domain.FunctionDefinition, error) {
if limit <= 0 {
limit = 50 // Default limit
}
if limit > 100 {
limit = 100 // Max limit
}
return s.functionRepo.List(ctx, appID, limit, offset)
}
func (s *functionService) GetByAppID(ctx context.Context, appID string) ([]*domain.FunctionDefinition, error) {
return s.functionRepo.GetByAppID(ctx, appID)
}
func (s *functionService) Deploy(ctx context.Context, id uuid.UUID, req *domain.DeployFunctionRequest, userID string) (*domain.DeployFunctionResponse, error) {
s.logger.Info("Deploying function",
zap.String("function_id", id.String()),
zap.String("user_id", userID),
zap.Bool("force", req.Force))
// Get function
function, err := s.functionRepo.GetByID(ctx, id)
if err != nil {
return nil, fmt.Errorf("function not found: %w", err)
}
// Get runtime backend
backend, err := s.runtimeService.GetBackend(ctx, string(function.Runtime))
if err != nil {
return nil, fmt.Errorf("failed to get runtime backend: %w", err)
}
// Deploy function
if err := backend.Deploy(ctx, function); err != nil {
s.logger.Error("Failed to deploy function",
zap.String("function_id", id.String()),
zap.Error(err))
return nil, fmt.Errorf("failed to deploy function: %w", err)
}
s.logger.Info("Function deployed successfully",
zap.String("function_id", id.String()),
zap.String("image", function.Image))
return &domain.DeployFunctionResponse{
Status: "deployed",
Message: "Function deployed successfully",
Image: function.Image,
}, nil
}
func (s *functionService) isValidRuntime(runtimeType string) bool {
validRuntimes := []string{
string(domain.RuntimeNodeJS18),
string(domain.RuntimePython39),
string(domain.RuntimeGo120),
string(domain.RuntimeCustom),
}
for _, valid := range validRuntimes {
if runtimeType == valid {
return true
}
}
return false
}

View File

@ -0,0 +1,48 @@
package services
import (
"context"
"github.com/RyanCopley/skybridge/faas/internal/domain"
"github.com/RyanCopley/skybridge/faas/internal/runtime"
"github.com/google/uuid"
)
// FunctionService provides business logic for function management
type FunctionService interface {
Create(ctx context.Context, req *domain.CreateFunctionRequest, userID string) (*domain.FunctionDefinition, error)
GetByID(ctx context.Context, id uuid.UUID) (*domain.FunctionDefinition, error)
GetByName(ctx context.Context, appID, name string) (*domain.FunctionDefinition, error)
Update(ctx context.Context, id uuid.UUID, req *domain.UpdateFunctionRequest, userID string) (*domain.FunctionDefinition, error)
Delete(ctx context.Context, id uuid.UUID, userID string) error
List(ctx context.Context, appID string, limit, offset int) ([]*domain.FunctionDefinition, error)
GetByAppID(ctx context.Context, appID string) ([]*domain.FunctionDefinition, error)
Deploy(ctx context.Context, id uuid.UUID, req *domain.DeployFunctionRequest, userID string) (*domain.DeployFunctionResponse, error)
}
// ExecutionService provides business logic for function execution
type ExecutionService interface {
Execute(ctx context.Context, req *domain.ExecuteFunctionRequest, userID string) (*domain.ExecuteFunctionResponse, error)
GetByID(ctx context.Context, id uuid.UUID) (*domain.FunctionExecution, error)
List(ctx context.Context, functionID *uuid.UUID, limit, offset int) ([]*domain.FunctionExecution, error)
GetByFunctionID(ctx context.Context, functionID uuid.UUID, limit, offset int) ([]*domain.FunctionExecution, error)
Cancel(ctx context.Context, id uuid.UUID, userID string) error
GetLogs(ctx context.Context, id uuid.UUID) ([]string, error)
GetRunningExecutions(ctx context.Context) ([]*domain.FunctionExecution, error)
}
// RuntimeService provides runtime management capabilities
type RuntimeService interface {
GetBackend(ctx context.Context, runtimeType string) (runtime.RuntimeBackend, error)
ListSupportedRuntimes(ctx context.Context) ([]*domain.RuntimeInfo, error)
HealthCheck(ctx context.Context, runtimeType string) error
GetRuntimeInfo(ctx context.Context, runtimeType string) (*runtime.RuntimeInfo, error)
ListContainers(ctx context.Context, runtimeType string) ([]runtime.ContainerInfo, error)
}
// AuthService provides authentication and authorization
type AuthService interface {
GetAuthContext(ctx context.Context) (*domain.AuthContext, error)
HasPermission(ctx context.Context, permission string) bool
ValidatePermissions(ctx context.Context, permissions []string) error
}

View File

@ -0,0 +1,194 @@
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 := docker.NewSimpleDockerRuntime(s.logger)
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
}