-
This commit is contained in:
261
faas/internal/handlers/execution.go
Normal file
261
faas/internal/handlers/execution.go
Normal file
@ -0,0 +1,261 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/google/uuid"
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/RyanCopley/skybridge/faas/internal/domain"
|
||||
"github.com/RyanCopley/skybridge/faas/internal/services"
|
||||
)
|
||||
|
||||
type ExecutionHandler struct {
|
||||
executionService services.ExecutionService
|
||||
authService services.AuthService
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
func NewExecutionHandler(executionService services.ExecutionService, authService services.AuthService, logger *zap.Logger) *ExecutionHandler {
|
||||
return &ExecutionHandler{
|
||||
executionService: executionService,
|
||||
authService: authService,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
func (h *ExecutionHandler) Execute(c *gin.Context) {
|
||||
idStr := c.Param("id")
|
||||
functionID, err := uuid.Parse(idStr)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid function ID"})
|
||||
return
|
||||
}
|
||||
|
||||
var req domain.ExecuteFunctionRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
h.logger.Warn("Invalid execute function request", zap.Error(err))
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request format"})
|
||||
return
|
||||
}
|
||||
req.FunctionID = functionID
|
||||
|
||||
authCtx, err := h.authService.GetAuthContext(c.Request.Context())
|
||||
if err != nil {
|
||||
h.logger.Error("Failed to get auth context", zap.Error(err))
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "Authentication required"})
|
||||
return
|
||||
}
|
||||
|
||||
if !h.authService.HasPermission(c.Request.Context(), "faas.execute") {
|
||||
c.JSON(http.StatusForbidden, gin.H{"error": "Insufficient permissions"})
|
||||
return
|
||||
}
|
||||
|
||||
response, err := h.executionService.Execute(c.Request.Context(), &req, authCtx.UserID)
|
||||
if err != nil {
|
||||
h.logger.Error("Failed to execute function", zap.String("function_id", idStr), zap.Error(err))
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
h.logger.Info("Function execution initiated",
|
||||
zap.String("function_id", functionID.String()),
|
||||
zap.String("execution_id", response.ExecutionID.String()),
|
||||
zap.String("user_id", authCtx.UserID),
|
||||
zap.Bool("async", req.Async))
|
||||
|
||||
c.JSON(http.StatusOK, response)
|
||||
}
|
||||
|
||||
func (h *ExecutionHandler) Invoke(c *gin.Context) {
|
||||
idStr := c.Param("id")
|
||||
functionID, err := uuid.Parse(idStr)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid function ID"})
|
||||
return
|
||||
}
|
||||
|
||||
var req domain.ExecuteFunctionRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
// Allow empty body
|
||||
req = domain.ExecuteFunctionRequest{
|
||||
FunctionID: functionID,
|
||||
Async: true,
|
||||
}
|
||||
}
|
||||
req.FunctionID = functionID
|
||||
req.Async = true // Force async for invoke endpoint
|
||||
|
||||
authCtx, err := h.authService.GetAuthContext(c.Request.Context())
|
||||
if err != nil {
|
||||
h.logger.Error("Failed to get auth context", zap.Error(err))
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "Authentication required"})
|
||||
return
|
||||
}
|
||||
|
||||
if !h.authService.HasPermission(c.Request.Context(), "faas.execute") {
|
||||
c.JSON(http.StatusForbidden, gin.H{"error": "Insufficient permissions"})
|
||||
return
|
||||
}
|
||||
|
||||
response, err := h.executionService.Execute(c.Request.Context(), &req, authCtx.UserID)
|
||||
if err != nil {
|
||||
h.logger.Error("Failed to invoke function", zap.String("function_id", idStr), zap.Error(err))
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
h.logger.Info("Function invoked successfully",
|
||||
zap.String("function_id", functionID.String()),
|
||||
zap.String("execution_id", response.ExecutionID.String()),
|
||||
zap.String("user_id", authCtx.UserID))
|
||||
|
||||
c.JSON(http.StatusAccepted, response)
|
||||
}
|
||||
|
||||
func (h *ExecutionHandler) GetByID(c *gin.Context) {
|
||||
idStr := c.Param("id")
|
||||
id, err := uuid.Parse(idStr)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid execution ID"})
|
||||
return
|
||||
}
|
||||
|
||||
if !h.authService.HasPermission(c.Request.Context(), "faas.read") {
|
||||
c.JSON(http.StatusForbidden, gin.H{"error": "Insufficient permissions"})
|
||||
return
|
||||
}
|
||||
|
||||
execution, err := h.executionService.GetByID(c.Request.Context(), id)
|
||||
if err != nil {
|
||||
h.logger.Error("Failed to get execution", zap.String("id", idStr), zap.Error(err))
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "Execution not found"})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, execution)
|
||||
}
|
||||
|
||||
func (h *ExecutionHandler) List(c *gin.Context) {
|
||||
if !h.authService.HasPermission(c.Request.Context(), "faas.read") {
|
||||
c.JSON(http.StatusForbidden, gin.H{"error": "Insufficient permissions"})
|
||||
return
|
||||
}
|
||||
|
||||
var functionID *uuid.UUID
|
||||
functionIDStr := c.Query("function_id")
|
||||
if functionIDStr != "" {
|
||||
id, err := uuid.Parse(functionIDStr)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid function ID"})
|
||||
return
|
||||
}
|
||||
functionID = &id
|
||||
}
|
||||
|
||||
limitStr := c.DefaultQuery("limit", "50")
|
||||
offsetStr := c.DefaultQuery("offset", "0")
|
||||
|
||||
limit, err := strconv.Atoi(limitStr)
|
||||
if err != nil || limit <= 0 {
|
||||
limit = 50
|
||||
}
|
||||
|
||||
offset, err := strconv.Atoi(offsetStr)
|
||||
if err != nil || offset < 0 {
|
||||
offset = 0
|
||||
}
|
||||
|
||||
executions, err := h.executionService.List(c.Request.Context(), functionID, limit, offset)
|
||||
if err != nil {
|
||||
h.logger.Error("Failed to list executions", zap.Error(err))
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"executions": executions,
|
||||
"limit": limit,
|
||||
"offset": offset,
|
||||
})
|
||||
}
|
||||
|
||||
func (h *ExecutionHandler) Cancel(c *gin.Context) {
|
||||
idStr := c.Param("id")
|
||||
id, err := uuid.Parse(idStr)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid execution ID"})
|
||||
return
|
||||
}
|
||||
|
||||
authCtx, err := h.authService.GetAuthContext(c.Request.Context())
|
||||
if err != nil {
|
||||
h.logger.Error("Failed to get auth context", zap.Error(err))
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "Authentication required"})
|
||||
return
|
||||
}
|
||||
|
||||
if !h.authService.HasPermission(c.Request.Context(), "faas.execute") {
|
||||
c.JSON(http.StatusForbidden, gin.H{"error": "Insufficient permissions"})
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.executionService.Cancel(c.Request.Context(), id, authCtx.UserID); err != nil {
|
||||
h.logger.Error("Failed to cancel execution", zap.String("id", idStr), zap.Error(err))
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
h.logger.Info("Execution canceled successfully",
|
||||
zap.String("execution_id", id.String()),
|
||||
zap.String("user_id", authCtx.UserID))
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "Execution canceled successfully"})
|
||||
}
|
||||
|
||||
func (h *ExecutionHandler) GetLogs(c *gin.Context) {
|
||||
idStr := c.Param("id")
|
||||
id, err := uuid.Parse(idStr)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid execution ID"})
|
||||
return
|
||||
}
|
||||
|
||||
if !h.authService.HasPermission(c.Request.Context(), "faas.read") {
|
||||
c.JSON(http.StatusForbidden, gin.H{"error": "Insufficient permissions"})
|
||||
return
|
||||
}
|
||||
|
||||
logs, err := h.executionService.GetLogs(c.Request.Context(), id)
|
||||
if err != nil {
|
||||
h.logger.Error("Failed to get execution logs", zap.String("id", idStr), zap.Error(err))
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"logs": logs,
|
||||
})
|
||||
}
|
||||
|
||||
func (h *ExecutionHandler) GetRunning(c *gin.Context) {
|
||||
if !h.authService.HasPermission(c.Request.Context(), "faas.read") {
|
||||
c.JSON(http.StatusForbidden, gin.H{"error": "Insufficient permissions"})
|
||||
return
|
||||
}
|
||||
|
||||
executions, err := h.executionService.GetRunningExecutions(c.Request.Context())
|
||||
if err != nil {
|
||||
h.logger.Error("Failed to get running executions", zap.Error(err))
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"executions": executions,
|
||||
"count": len(executions),
|
||||
})
|
||||
}
|
||||
244
faas/internal/handlers/function.go
Normal file
244
faas/internal/handlers/function.go
Normal file
@ -0,0 +1,244 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/google/uuid"
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/RyanCopley/skybridge/faas/internal/domain"
|
||||
"github.com/RyanCopley/skybridge/faas/internal/services"
|
||||
)
|
||||
|
||||
type FunctionHandler struct {
|
||||
functionService services.FunctionService
|
||||
authService services.AuthService
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
func NewFunctionHandler(functionService services.FunctionService, authService services.AuthService, logger *zap.Logger) *FunctionHandler {
|
||||
return &FunctionHandler{
|
||||
functionService: functionService,
|
||||
authService: authService,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
func (h *FunctionHandler) Create(c *gin.Context) {
|
||||
var req domain.CreateFunctionRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
h.logger.Warn("Invalid create function request", zap.Error(err))
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request format"})
|
||||
return
|
||||
}
|
||||
|
||||
// Auto-select image based on runtime if not provided or empty
|
||||
if req.Image == "" {
|
||||
if runtimeConfig, exists := domain.GetRuntimeConfig(req.Runtime); exists && runtimeConfig.Image != "" {
|
||||
req.Image = runtimeConfig.Image
|
||||
}
|
||||
}
|
||||
|
||||
authCtx, err := h.authService.GetAuthContext(c.Request.Context())
|
||||
if err != nil {
|
||||
h.logger.Error("Failed to get auth context", zap.Error(err))
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "Authentication required"})
|
||||
return
|
||||
}
|
||||
|
||||
if !h.authService.HasPermission(c.Request.Context(), "faas.write") {
|
||||
c.JSON(http.StatusForbidden, gin.H{"error": "Insufficient permissions"})
|
||||
return
|
||||
}
|
||||
|
||||
function, err := h.functionService.Create(c.Request.Context(), &req, authCtx.UserID)
|
||||
if err != nil {
|
||||
h.logger.Error("Failed to create function", zap.Error(err))
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
h.logger.Info("Function created successfully",
|
||||
zap.String("function_id", function.ID.String()),
|
||||
zap.String("name", function.Name),
|
||||
zap.String("user_id", authCtx.UserID))
|
||||
|
||||
c.JSON(http.StatusCreated, function)
|
||||
}
|
||||
|
||||
func (h *FunctionHandler) GetByID(c *gin.Context) {
|
||||
idStr := c.Param("id")
|
||||
id, err := uuid.Parse(idStr)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid function ID"})
|
||||
return
|
||||
}
|
||||
|
||||
if !h.authService.HasPermission(c.Request.Context(), "faas.read") {
|
||||
c.JSON(http.StatusForbidden, gin.H{"error": "Insufficient permissions"})
|
||||
return
|
||||
}
|
||||
|
||||
function, err := h.functionService.GetByID(c.Request.Context(), id)
|
||||
if err != nil {
|
||||
h.logger.Error("Failed to get function", zap.String("id", idStr), zap.Error(err))
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "Function not found"})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, function)
|
||||
}
|
||||
|
||||
func (h *FunctionHandler) Update(c *gin.Context) {
|
||||
idStr := c.Param("id")
|
||||
id, err := uuid.Parse(idStr)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid function ID"})
|
||||
return
|
||||
}
|
||||
|
||||
var req domain.UpdateFunctionRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
h.logger.Warn("Invalid update function request", zap.Error(err))
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request format"})
|
||||
return
|
||||
}
|
||||
|
||||
authCtx, err := h.authService.GetAuthContext(c.Request.Context())
|
||||
if err != nil {
|
||||
h.logger.Error("Failed to get auth context", zap.Error(err))
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "Authentication required"})
|
||||
return
|
||||
}
|
||||
|
||||
if !h.authService.HasPermission(c.Request.Context(), "faas.write") {
|
||||
c.JSON(http.StatusForbidden, gin.H{"error": "Insufficient permissions"})
|
||||
return
|
||||
}
|
||||
|
||||
function, err := h.functionService.Update(c.Request.Context(), id, &req, authCtx.UserID)
|
||||
if err != nil {
|
||||
h.logger.Error("Failed to update function", zap.String("id", idStr), zap.Error(err))
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
h.logger.Info("Function updated successfully",
|
||||
zap.String("function_id", id.String()),
|
||||
zap.String("user_id", authCtx.UserID))
|
||||
|
||||
c.JSON(http.StatusOK, function)
|
||||
}
|
||||
|
||||
func (h *FunctionHandler) Delete(c *gin.Context) {
|
||||
idStr := c.Param("id")
|
||||
id, err := uuid.Parse(idStr)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid function ID"})
|
||||
return
|
||||
}
|
||||
|
||||
authCtx, err := h.authService.GetAuthContext(c.Request.Context())
|
||||
if err != nil {
|
||||
h.logger.Error("Failed to get auth context", zap.Error(err))
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "Authentication required"})
|
||||
return
|
||||
}
|
||||
|
||||
if !h.authService.HasPermission(c.Request.Context(), "faas.delete") {
|
||||
c.JSON(http.StatusForbidden, gin.H{"error": "Insufficient permissions"})
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.functionService.Delete(c.Request.Context(), id, authCtx.UserID); err != nil {
|
||||
h.logger.Error("Failed to delete function", zap.String("id", idStr), zap.Error(err))
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
h.logger.Info("Function deleted successfully",
|
||||
zap.String("function_id", id.String()),
|
||||
zap.String("user_id", authCtx.UserID))
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "Function deleted successfully"})
|
||||
}
|
||||
|
||||
func (h *FunctionHandler) List(c *gin.Context) {
|
||||
if !h.authService.HasPermission(c.Request.Context(), "faas.read") {
|
||||
c.JSON(http.StatusForbidden, gin.H{"error": "Insufficient permissions"})
|
||||
return
|
||||
}
|
||||
|
||||
appID := c.Query("app_id")
|
||||
limitStr := c.DefaultQuery("limit", "50")
|
||||
offsetStr := c.DefaultQuery("offset", "0")
|
||||
|
||||
limit, err := strconv.Atoi(limitStr)
|
||||
if err != nil || limit <= 0 {
|
||||
limit = 50
|
||||
}
|
||||
|
||||
offset, err := strconv.Atoi(offsetStr)
|
||||
if err != nil || offset < 0 {
|
||||
offset = 0
|
||||
}
|
||||
|
||||
functions, err := h.functionService.List(c.Request.Context(), appID, limit, offset)
|
||||
if err != nil {
|
||||
h.logger.Error("Failed to list functions", zap.Error(err))
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"functions": functions,
|
||||
"limit": limit,
|
||||
"offset": offset,
|
||||
})
|
||||
}
|
||||
|
||||
func (h *FunctionHandler) Deploy(c *gin.Context) {
|
||||
idStr := c.Param("id")
|
||||
id, err := uuid.Parse(idStr)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid function ID"})
|
||||
return
|
||||
}
|
||||
|
||||
var req domain.DeployFunctionRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
// Allow empty body for deploy
|
||||
req = domain.DeployFunctionRequest{
|
||||
FunctionID: id,
|
||||
Force: false,
|
||||
}
|
||||
}
|
||||
req.FunctionID = id
|
||||
|
||||
authCtx, err := h.authService.GetAuthContext(c.Request.Context())
|
||||
if err != nil {
|
||||
h.logger.Error("Failed to get auth context", zap.Error(err))
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "Authentication required"})
|
||||
return
|
||||
}
|
||||
|
||||
if !h.authService.HasPermission(c.Request.Context(), "faas.deploy") {
|
||||
c.JSON(http.StatusForbidden, gin.H{"error": "Insufficient permissions"})
|
||||
return
|
||||
}
|
||||
|
||||
response, err := h.functionService.Deploy(c.Request.Context(), id, &req, authCtx.UserID)
|
||||
if err != nil {
|
||||
h.logger.Error("Failed to deploy function", zap.String("id", idStr), zap.Error(err))
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
h.logger.Info("Function deployed successfully",
|
||||
zap.String("function_id", id.String()),
|
||||
zap.String("user_id", authCtx.UserID))
|
||||
|
||||
c.JSON(http.StatusOK, response)
|
||||
}
|
||||
70
faas/internal/handlers/health.go
Normal file
70
faas/internal/handlers/health.go
Normal file
@ -0,0 +1,70 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
type HealthHandler struct {
|
||||
db *sql.DB
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
func NewHealthHandler(db *sql.DB, logger *zap.Logger) *HealthHandler {
|
||||
return &HealthHandler{
|
||||
db: db,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
func (h *HealthHandler) Health(c *gin.Context) {
|
||||
h.logger.Debug("Health check requested")
|
||||
|
||||
response := gin.H{
|
||||
"status": "healthy",
|
||||
"service": "faas",
|
||||
"timestamp": time.Now().UTC().Format(time.RFC3339),
|
||||
"version": "1.0.0",
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, response)
|
||||
}
|
||||
|
||||
func (h *HealthHandler) Ready(c *gin.Context) {
|
||||
h.logger.Debug("Readiness check requested")
|
||||
|
||||
checks := make(map[string]interface{})
|
||||
overall := "ready"
|
||||
|
||||
// Check database connection
|
||||
if err := h.db.Ping(); err != nil {
|
||||
h.logger.Error("Database health check failed", zap.Error(err))
|
||||
checks["database"] = gin.H{
|
||||
"status": "unhealthy",
|
||||
"error": err.Error(),
|
||||
}
|
||||
overall = "not ready"
|
||||
} else {
|
||||
checks["database"] = gin.H{
|
||||
"status": "healthy",
|
||||
}
|
||||
}
|
||||
|
||||
response := gin.H{
|
||||
"status": overall,
|
||||
"service": "faas",
|
||||
"timestamp": time.Now().UTC().Format(time.RFC3339),
|
||||
"checks": checks,
|
||||
}
|
||||
|
||||
statusCode := http.StatusOK
|
||||
if overall != "ready" {
|
||||
statusCode = http.StatusServiceUnavailable
|
||||
}
|
||||
|
||||
c.JSON(statusCode, response)
|
||||
}
|
||||
Reference in New Issue
Block a user