api: regenerate OpenAPI types and server code

- Update openapi.yaml spec
- Regenerate server_gen.go with oapi-codegen
- Update adapter, routes, and server configuration
This commit is contained in:
Jeremie Fraeys 2026-03-04 13:23:34 -05:00
parent 743bc4be3b
commit 1f495dfbb7
No known key found for this signature in database
6 changed files with 1731 additions and 155 deletions

View file

@ -62,6 +62,30 @@ paths:
schema:
type: integer
default: 0
- name: user_id
in: query
schema:
type: string
description: Filter by user who submitted the task
- name: plugin_configs
in: query
schema:
type: object
additionalProperties:
$ref: '#/components/schemas/PluginConfig'
description: Plugin configurations for this task
- name: node_count
in: query
schema:
type: integer
minimum: 1
default: 1
description: Number of nodes for multi-node jobs
- name: reservation_id
in: query
schema:
type: string
description: Pre-reserved capacity for this task
responses:
'200':
description: List of tasks
@ -141,6 +165,458 @@ paths:
application/json:
schema:
$ref: '#/components/schemas/QueueStats'
'401':
$ref: '#/components/responses/Unauthorized'
/v1/plugins:
get:
summary: List available plugins
description: Returns all registered plugins and their status
tags:
- Plugins
responses:
'200':
description: List of plugins
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/Plugin'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
/v1/plugins/{pluginName}:
get:
summary: Get plugin details
description: Returns plugin configuration and status
tags:
- Plugins
parameters:
- name: pluginName
in: path
required: true
schema:
type: string
responses:
'200':
description: Plugin details
content:
application/json:
schema:
$ref: '#/components/schemas/Plugin'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
/v1/plugins/{pluginName}/config:
get:
summary: Get plugin configuration
description: Returns plugin configuration
tags:
- Plugins
parameters:
- name: pluginName
in: path
required: true
schema:
type: string
responses:
'200':
description: Plugin configuration
content:
application/json:
schema:
$ref: '#/components/schemas/PluginConfig'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
put:
summary: Update plugin configuration
description: Update plugin configuration (hot-reload if supported)
tags:
- Plugins
parameters:
- name: pluginName
in: path
required: true
schema:
type: string
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/PluginConfig'
responses:
'200':
description: Configuration updated
content:
application/json:
schema:
$ref: '#/components/schemas/Plugin'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
'422':
$ref: '#/components/responses/ValidationError'
delete:
summary: Disable/unload plugin
description: Disable plugin (may require restart if plugin requires it)
tags:
- Plugins
parameters:
- name: pluginName
in: path
required: true
schema:
type: string
responses:
'204':
description: Plugin disabled
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
/v1/plugins/{pluginName}/health:
get:
summary: Check plugin health
description: Returns health status of plugin sidecars
tags:
- Plugins
parameters:
- name: pluginName
in: path
required: true
schema:
type: string
responses:
'200':
description: Plugin health
content:
application/json:
schema:
$ref: '#/components/schemas/HealthResponse'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
/v1/scheduler/status:
get:
summary: Get scheduler status
description: Returns queue depths, worker counts, and metrics
tags:
- Scheduler
responses:
'200':
description: Scheduler status
content:
application/json:
schema:
$ref: '#/components/schemas/SchedulerStatus'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
/v1/scheduler/status/stream:
get:
summary: SSE stream of scheduler state changes
description: Emits events on queue depth changes, worker connect/disconnect, job transitions
tags:
- Scheduler
produces:
- text/event-stream
responses:
'200':
description: SSE stream
content:
text/event-stream:
schema:
type: string
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
/v1/scheduler/workers:
get:
summary: List connected workers
description: Returns all workers and their capabilities
tags:
- Scheduler
responses:
'200':
description: List of workers
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/Worker'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
/v1/scheduler/workers/{workerId}:
get:
summary: Get worker details
description: Returns detailed worker information
tags:
- Scheduler
parameters:
- name: workerId
in: path
required: true
schema:
type: string
responses:
'200':
description: Worker details
content:
application/json:
schema:
$ref: '#/components/schemas/Worker'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
delete:
summary: Disconnect/drain worker
description: Gracefully drain and disconnect a worker
tags:
- Scheduler
parameters:
- name: workerId
in: path
required: true
schema:
type: string
responses:
'204':
description: Worker draining initiated
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
/v1/scheduler/reservations:
get:
summary: List active reservations
description: Returns all active capacity reservations
tags:
- Scheduler
responses:
'200':
description: List of reservations
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/Reservation'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
post:
summary: Create reservation
description: Reserve capacity for large jobs
tags:
- Scheduler
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/CreateReservationRequest'
responses:
'201':
description: Reservation created
content:
application/json:
schema:
$ref: '#/components/schemas/Reservation'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'422':
$ref: '#/components/responses/ValidationError'
/v1/scheduler/jobs/{jobId}/priority:
patch:
summary: Update job priority
description: Change the priority of a queued or running job
tags:
- Scheduler
parameters:
- name: jobId
in: path
required: true
schema:
type: string
requestBody:
required: true
content:
application/json:
schema:
type: object
required:
- priority
properties:
priority:
type: integer
minimum: 1
maximum: 10
responses:
'200':
description: Priority updated
content:
application/json:
schema:
$ref: '#/components/schemas/Task'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
/v1/scheduler/jobs/{jobId}/stream:
get:
summary: SSE stream of job progress
description: Emits events on job state transitions and priority changes
tags:
- Scheduler
produces:
- text/event-stream
parameters:
- name: jobId
in: path
required: true
schema:
type: string
responses:
'200':
description: SSE stream
content:
text/event-stream:
schema:
type: string
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
/v1/audit/events:
get:
summary: Query audit events
description: Filter by time range, event type, user
tags:
- Audit
parameters:
- name: from
in: query
schema:
type: string
format: date-time
description: Start timestamp
- name: to
in: query
schema:
type: string
format: date-time
description: End timestamp
- name: event_type
in: query
schema:
type: string
enum: [job_queued, job_started, job_completed, file_access, auth_attempt, plugin_configured, scheduler_drain, audit_verified]
description: Filter by event type
- name: user_id
in: query
schema:
type: string
description: Filter by user
- name: limit
in: query
schema:
type: integer
default: 100
maximum: 1000
- name: offset
in: query
schema:
type: integer
default: 0
responses:
'200':
description: List of audit events
content:
application/json:
schema:
$ref: '#/components/schemas/AuditEventList'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'429':
$ref: '#/components/responses/RateLimited'
/v1/audit/verify:
post:
summary: Verify audit chain integrity
description: Validates the hash chain for tampering
tags:
- Audit
responses:
'200':
description: Verification result
content:
application/json:
schema:
$ref: '#/components/schemas/VerificationResult'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
/v1/audit/chain-root:
get:
summary: Get current chain root hash
description: Returns the latest event hash for external verification
tags:
- Audit
responses:
'200':
description: Chain root hash
content:
application/json:
schema:
type: object
properties:
root_hash:
type: string
timestamp:
type: string
format: date-time
total_events:
type: integer
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
/v1/experiments:
get:
summary: List experiments
@ -276,6 +752,9 @@ components:
type: string
output:
type: string
entrypoint:
type: string
description: Entrypoint script or command executed for this task
snapshot_id:
type: string
datasets:
@ -294,6 +773,19 @@ components:
type: integer
max_retries:
type: integer
plugin_status:
type: object
additionalProperties:
$ref: '#/components/schemas/PluginStatus'
description: Status of tracking plugins for this task
node_assignments:
type: array
items:
$ref: '#/components/schemas/NodeAssignment'
description: Worker node assignments for multi-node jobs
priority_aged:
type: number
description: Effective priority with aging applied
CreateTaskRequest:
type: object
required:
@ -311,7 +803,14 @@ components:
default: 5
args:
type: string
description: Command-line arguments for the training script
description: Command-line arguments for the entrypoint
entrypoint:
type: string
description: Entrypoint script or command (e.g., train.py, run.sh, /bin/bash -c "echo hello")
examples:
- train.py
- run.sh
- /bin/bash -c "python train.py --epochs 10"
snapshot_id:
type: string
description: Reference to experiment snapshot
@ -433,6 +932,232 @@ components:
image:
type: string
default: jupyter/pytorch:latest
PluginConfig:
type: object
properties:
enabled:
type: boolean
mode:
type: string
enum: [sidecar, remote, disabled]
image:
type: string
settings:
type: object
additionalProperties: true
PluginStatus:
type: object
properties:
name:
type: string
status:
type: string
enum: [healthy, unhealthy, starting, stopped]
url:
type: string
last_check:
type: string
format: date-time
NodeAssignment:
type: object
properties:
node_id:
type: integer
worker_id:
type: string
slot_assigned:
type: boolean
Plugin:
type: object
properties:
name:
type: string
description: Plugin name
enabled:
type: boolean
description: Whether plugin is enabled
mode:
type: string
enum: [sidecar, remote, disabled]
description: Provisioning mode
status:
$ref: '#/components/schemas/PluginStatus'
description: Current plugin status
config:
$ref: '#/components/schemas/PluginConfig'
requires_restart:
type: boolean
description: Whether plugin requires restart on config change
version:
type: string
description: Plugin version
Worker:
type: object
properties:
id:
type: string
description: Worker unique identifier
connected_at:
type: string
format: date-time
description: When worker connected
last_heartbeat:
type: string
format: date-time
description: Last heartbeat timestamp
capabilities:
type: object
properties:
gpu_count:
type: integer
description: Number of GPUs
gpu_type:
type: string
description: GPU type (e.g., A100, H100)
cpu_cores:
type: integer
memory_gb:
type: integer
slots:
type: object
properties:
batch_available:
type: integer
batch_total:
type: integer
service_available:
type: integer
service_total:
type: integer
active_tasks:
type: array
items:
type: string
description: IDs of tasks currently running on this worker
status:
type: string
enum: [active, draining, offline]
SchedulerStatus:
type: object
properties:
workers_total:
type: integer
workers_active:
type: integer
workers_draining:
type: integer
batch_queue_depth:
type: integer
service_queue_depth:
type: integer
tasks_running:
type: integer
tasks_completed_24h:
type: integer
reservations_active:
type: integer
timestamp:
type: string
format: date-time
Reservation:
type: object
properties:
id:
type: string
user_id:
type: string
gpu_count:
type: integer
gpu_type:
type: string
node_count:
type: integer
expires_at:
type: string
format: date-time
status:
type: string
enum: [active, claimed, expired]
CreateReservationRequest:
type: object
required:
- gpu_count
properties:
gpu_count:
type: integer
minimum: 1
gpu_type:
type: string
node_count:
type: integer
minimum: 1
default: 1
expires_minutes:
type: integer
default: 30
AuditEvent:
type: object
properties:
timestamp:
type: string
format: date-time
event_type:
type: string
enum: [job_queued, job_started, job_completed, file_access, auth_attempt, plugin_configured, scheduler_drain, audit_verified]
user_id:
type: string
resource:
type: string
description: Resource being acted upon
action:
type: string
description: Action performed
success:
type: boolean
ip_address:
type: string
error:
type: string
prev_hash:
type: string
description: Previous event hash in chain
event_hash:
type: string
description: This event's hash
sequence_num:
type: integer
description: Position in audit chain
metadata:
type: object
additionalProperties: true
AuditEventList:
type: object
properties:
events:
type: array
items:
$ref: '#/components/schemas/AuditEvent'
total:
type: integer
limit:
type: integer
offset:
type: integer
VerificationResult:
type: object
properties:
valid:
type: boolean
total_events:
type: integer
first_tampered:
type: integer
description: Sequence number of first tampered event (if any)
chain_root_hash:
type: string
verified_at:
type: string
format: date-time
ErrorResponse:
type: object
required:
@ -449,6 +1174,13 @@ components:
trace_id:
type: string
description: Support correlation ID
tags:
- name: Plugins
description: Plugin management endpoints
- name: Scheduler
description: Scheduler and worker management
- name: Audit
description: Audit log and chain verification
responses:
BadRequest:
description: Invalid request

View file

@ -4,17 +4,23 @@ package api
import (
"net/http"
"github.com/jfraeys/fetch_ml/internal/api/audit"
"github.com/jfraeys/fetch_ml/internal/api/datasets"
"github.com/jfraeys/fetch_ml/internal/api/jobs"
"github.com/jfraeys/fetch_ml/internal/api/jupyter"
"github.com/jfraeys/fetch_ml/internal/api/plugins"
sch "github.com/jfraeys/fetch_ml/internal/api/scheduler"
"github.com/labstack/echo/v4"
)
// HandlerAdapter implements the generated ServerInterface using existing handlers
type HandlerAdapter struct {
jobsHandler *jobs.Handler
jupyterHandler *jupyter.Handler
datasetsHandler *datasets.Handler
jobsHandler *jobs.Handler
jupyterHandler *jupyter.Handler
datasetsHandler *datasets.Handler
pluginsHandler *plugins.Handler
schedulerHandler *sch.APIHandler
auditHandler *audit.Handler
}
// NewHandlerAdapter creates a new handler adapter
@ -22,11 +28,17 @@ func NewHandlerAdapter(
jobsHandler *jobs.Handler,
jupyterHandler *jupyter.Handler,
datasetsHandler *datasets.Handler,
pluginsHandler *plugins.Handler,
schedulerHandler *sch.APIHandler,
auditHandler *audit.Handler,
) *HandlerAdapter {
return &HandlerAdapter{
jobsHandler: jobsHandler,
jupyterHandler: jupyterHandler,
datasetsHandler: datasetsHandler,
jobsHandler: jobsHandler,
jupyterHandler: jupyterHandler,
datasetsHandler: datasetsHandler,
pluginsHandler: pluginsHandler,
schedulerHandler: schedulerHandler,
auditHandler: auditHandler,
}
}
@ -154,3 +166,207 @@ func (a *HandlerAdapter) GetWs(ctx echo.Context) error {
"message": "Use WebSocket protocol to connect to this endpoint",
})
}
// Plugin handlers
// GetV1Plugins lists all plugins
func (a *HandlerAdapter) GetV1Plugins(ctx echo.Context) error {
if a.pluginsHandler == nil {
return ctx.JSON(503, map[string]any{
"error": "Plugin service not available",
"code": "SERVICE_UNAVAILABLE",
})
}
return toHTTPHandler(a.pluginsHandler.GetV1Plugins)(ctx)
}
// GetV1PluginsPluginName gets plugin details
func (a *HandlerAdapter) GetV1PluginsPluginName(ctx echo.Context, pluginName string) error {
if a.pluginsHandler == nil {
return ctx.JSON(503, map[string]any{
"error": "Plugin service not available",
"code": "SERVICE_UNAVAILABLE",
})
}
return toHTTPHandler(a.pluginsHandler.GetV1PluginsPluginName)(ctx)
}
// GetV1PluginsPluginNameConfig gets plugin configuration
func (a *HandlerAdapter) GetV1PluginsPluginNameConfig(ctx echo.Context, pluginName string) error {
if a.pluginsHandler == nil {
return ctx.JSON(503, map[string]any{
"error": "Plugin service not available",
"code": "SERVICE_UNAVAILABLE",
})
}
return toHTTPHandler(a.pluginsHandler.GetV1PluginsPluginNameConfig)(ctx)
}
// PutV1PluginsPluginNameConfig updates plugin configuration
func (a *HandlerAdapter) PutV1PluginsPluginNameConfig(ctx echo.Context, pluginName string) error {
if a.pluginsHandler == nil {
return ctx.JSON(503, map[string]any{
"error": "Plugin service not available",
"code": "SERVICE_UNAVAILABLE",
})
}
return toHTTPHandler(a.pluginsHandler.PutV1PluginsPluginNameConfig)(ctx)
}
// DeleteV1PluginsPluginNameConfig disables/unloads plugin
func (a *HandlerAdapter) DeleteV1PluginsPluginNameConfig(ctx echo.Context, pluginName string) error {
if a.pluginsHandler == nil {
return ctx.JSON(503, map[string]any{
"error": "Plugin service not available",
"code": "SERVICE_UNAVAILABLE",
})
}
return toHTTPHandler(a.pluginsHandler.DeleteV1PluginsPluginNameConfig)(ctx)
}
// GetV1PluginsPluginNameHealth checks plugin health
func (a *HandlerAdapter) GetV1PluginsPluginNameHealth(ctx echo.Context, pluginName string) error {
if a.pluginsHandler == nil {
return ctx.JSON(503, map[string]any{
"error": "Plugin service not available",
"code": "SERVICE_UNAVAILABLE",
})
}
return toHTTPHandler(a.pluginsHandler.GetV1PluginsPluginNameHealth)(ctx)
}
// Scheduler handlers
// GetV1SchedulerStatus gets scheduler status
func (a *HandlerAdapter) GetV1SchedulerStatus(ctx echo.Context) error {
if a.schedulerHandler == nil {
return ctx.JSON(503, map[string]any{
"error": "Scheduler service not available",
"code": "SERVICE_UNAVAILABLE",
})
}
return toHTTPHandler(a.schedulerHandler.GetV1SchedulerStatus)(ctx)
}
// GetV1SchedulerStatusStream gets scheduler status stream (SSE)
func (a *HandlerAdapter) GetV1SchedulerStatusStream(ctx echo.Context) error {
if a.schedulerHandler == nil {
return ctx.JSON(503, map[string]any{
"error": "Scheduler service not available",
"code": "SERVICE_UNAVAILABLE",
})
}
return toHTTPHandler(a.schedulerHandler.GetV1SchedulerStatusStream)(ctx)
}
// GetV1SchedulerWorkers lists connected workers
func (a *HandlerAdapter) GetV1SchedulerWorkers(ctx echo.Context) error {
if a.schedulerHandler == nil {
return ctx.JSON(503, map[string]any{
"error": "Scheduler service not available",
"code": "SERVICE_UNAVAILABLE",
})
}
return toHTTPHandler(a.schedulerHandler.GetV1SchedulerWorkers)(ctx)
}
// GetV1SchedulerWorkersWorkerId gets worker details
func (a *HandlerAdapter) GetV1SchedulerWorkersWorkerId(ctx echo.Context, workerId string) error {
if a.schedulerHandler == nil {
return ctx.JSON(503, map[string]any{
"error": "Scheduler service not available",
"code": "SERVICE_UNAVAILABLE",
})
}
return toHTTPHandler(a.schedulerHandler.GetV1SchedulerWorkersWorkerID)(ctx)
}
// DeleteV1SchedulerWorkersWorkerId drains a worker
func (a *HandlerAdapter) DeleteV1SchedulerWorkersWorkerId(ctx echo.Context, workerId string) error {
if a.schedulerHandler == nil {
return ctx.JSON(503, map[string]any{
"error": "Scheduler service not available",
"code": "SERVICE_UNAVAILABLE",
})
}
return toHTTPHandler(a.schedulerHandler.DeleteV1SchedulerWorkersWorkerID)(ctx)
}
// GetV1SchedulerReservations lists active reservations
func (a *HandlerAdapter) GetV1SchedulerReservations(ctx echo.Context) error {
if a.schedulerHandler == nil {
return ctx.JSON(503, map[string]any{
"error": "Scheduler service not available",
"code": "SERVICE_UNAVAILABLE",
})
}
return toHTTPHandler(a.schedulerHandler.GetV1SchedulerReservations)(ctx)
}
// PostV1SchedulerReservations creates a reservation
func (a *HandlerAdapter) PostV1SchedulerReservations(ctx echo.Context) error {
if a.schedulerHandler == nil {
return ctx.JSON(503, map[string]any{
"error": "Scheduler service not available",
"code": "SERVICE_UNAVAILABLE",
})
}
return toHTTPHandler(a.schedulerHandler.PostV1SchedulerReservations)(ctx)
}
// PatchV1SchedulerJobsJobIdPriority updates job priority
func (a *HandlerAdapter) PatchV1SchedulerJobsJobIdPriority(ctx echo.Context, jobId string) error {
if a.schedulerHandler == nil {
return ctx.JSON(503, map[string]any{
"error": "Scheduler service not available",
"code": "SERVICE_UNAVAILABLE",
})
}
return toHTTPHandler(a.schedulerHandler.PatchV1SchedulerJobsJobIDPriority)(ctx)
}
// GetV1SchedulerJobsJobIdStream gets job progress stream (SSE)
func (a *HandlerAdapter) GetV1SchedulerJobsJobIdStream(ctx echo.Context, jobId string) error {
if a.schedulerHandler == nil {
return ctx.JSON(503, map[string]any{
"error": "Scheduler service not available",
"code": "SERVICE_UNAVAILABLE",
})
}
return toHTTPHandler(a.schedulerHandler.GetV1SchedulerJobsJobIDStream)(ctx)
}
// Audit handlers
// GetV1AuditEvents queries audit events
func (a *HandlerAdapter) GetV1AuditEvents(ctx echo.Context, params GetV1AuditEventsParams) error {
if a.auditHandler == nil {
return ctx.JSON(503, map[string]any{
"error": "Audit service not available",
"code": "SERVICE_UNAVAILABLE",
})
}
return toHTTPHandler(a.auditHandler.GetV1AuditEvents)(ctx)
}
// PostV1AuditVerify verifies audit chain integrity
func (a *HandlerAdapter) PostV1AuditVerify(ctx echo.Context) error {
if a.auditHandler == nil {
return ctx.JSON(503, map[string]any{
"error": "Audit service not available",
"code": "SERVICE_UNAVAILABLE",
})
}
return toHTTPHandler(a.auditHandler.PostV1AuditVerify)(ctx)
}
// GetV1AuditChainRoot gets chain root hash
func (a *HandlerAdapter) GetV1AuditChainRoot(ctx echo.Context) error {
if a.auditHandler == nil {
return ctx.JSON(503, map[string]any{
"error": "Audit service not available",
"code": "SERVICE_UNAVAILABLE",
})
}
return toHTTPHandler(a.auditHandler.GetV1AuditChainRoot)(ctx)
}

View file

@ -4,9 +4,12 @@ import (
"net/http"
"os"
"github.com/jfraeys/fetch_ml/internal/api/audit"
"github.com/jfraeys/fetch_ml/internal/api/datasets"
"github.com/jfraeys/fetch_ml/internal/api/jobs"
"github.com/jfraeys/fetch_ml/internal/api/jupyter"
"github.com/jfraeys/fetch_ml/internal/api/plugins"
sch "github.com/jfraeys/fetch_ml/internal/api/scheduler"
"github.com/jfraeys/fetch_ml/internal/api/ws"
"github.com/jfraeys/fetch_ml/internal/prommetrics"
"github.com/labstack/echo/v4"
@ -55,8 +58,18 @@ func (s *Server) registerRoutes(mux *http.ServeMux) {
// Register OpenAPI-generated routes with Echo router
s.registerOpenAPIRoutes(mux, jobsHandler)
// Register scheduler API endpoints
schedulerHandler := sch.NewHandler(s.schedulerHub, s.logger, s.config.BuildAuthConfig())
mux.HandleFunc("GET /api/v1/workers", schedulerHandler.GetV1SchedulerWorkers)
mux.HandleFunc("GET /api/v1/workers/{workerId}", schedulerHandler.GetV1SchedulerWorkersWorkerID)
mux.HandleFunc("DELETE /api/v1/workers/{workerId}", schedulerHandler.DeleteV1SchedulerWorkersWorkerID)
// Register API documentation endpoint
s.registerDocsRoutes(mux)
// Register OpenAPI spec endpoint
mux.HandleFunc("GET /api/openapi.yaml", ServeOpenAPISpec)
s.logger.Info("OpenAPI spec endpoint registered", "path", "/api/openapi.yaml")
}
// registerDocsRoutes sets up API documentation serving
@ -90,16 +103,42 @@ func (s *Server) registerOpenAPIRoutes(mux *http.ServeMux, jobsHandler *jobs.Han
s.config.DataDir,
)
// Create plugins handler
pluginsHandler := plugins.NewHandler(
s.logger,
s.trackingRegistry, // Need to add this to Server
nil, // Plugin config - can be loaded from config
)
// Create scheduler handler
var schedulerHandler *sch.APIHandler
if s.schedulerHub != nil { // Need to add this to Server
schedulerHandler = sch.NewAPIHandler(s.logger, s.schedulerHub)
}
// Create audit handler
auditHandler := audit.NewHandler(s.logger, nil) // Audit store can be added later
// Create adapter implementing ServerInterface
handlerAdapter := NewHandlerAdapter(
jobsHandler,
jupyterHandler,
datasetsHandler,
pluginsHandler,
schedulerHandler,
auditHandler,
)
// Register generated OpenAPI routes
RegisterHandlers(e, handlerAdapter)
// Add scheduler workers endpoint directly to main mux (not Echo)
if schedulerHandler != nil {
mux.HandleFunc("GET /api/v1/workers", schedulerHandler.GetV1SchedulerWorkers)
mux.HandleFunc("GET /api/v1/workers/{workerId}", schedulerHandler.GetV1SchedulerWorkersWorkerID)
mux.HandleFunc("DELETE /api/v1/workers/{workerId}", schedulerHandler.DeleteV1SchedulerWorkersWorkerID)
}
// Wrap Echo router to work with existing middleware chain
echoHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
e.ServeHTTP(w, r)

View file

@ -16,7 +16,9 @@ import (
"github.com/jfraeys/fetch_ml/internal/middleware"
"github.com/jfraeys/fetch_ml/internal/prommetrics"
"github.com/jfraeys/fetch_ml/internal/queue"
"github.com/jfraeys/fetch_ml/internal/scheduler"
"github.com/jfraeys/fetch_ml/internal/storage"
"github.com/jfraeys/fetch_ml/internal/tracking"
)
// Server represents the API server
@ -32,6 +34,8 @@ type Server struct {
auditLogger *audit.Logger
promMetrics *prommetrics.Metrics
validationMiddleware *apimiddleware.ValidationMiddleware
trackingRegistry *tracking.Registry
schedulerHub *scheduler.SchedulerHub
cleanupFuncs []func()
}

View file

@ -8,6 +8,7 @@ import (
"github.com/jfraeys/fetch_ml/internal/auth"
"github.com/jfraeys/fetch_ml/internal/config"
"github.com/jfraeys/fetch_ml/internal/crypto/kms"
"github.com/jfraeys/fetch_ml/internal/fileutil"
"github.com/jfraeys/fetch_ml/internal/logging"
"github.com/jfraeys/fetch_ml/internal/storage"
@ -34,6 +35,7 @@ type ServerConfig struct {
Redis RedisConfig `yaml:"redis"`
Resources config.ResourceConfig `yaml:"resources"`
Security SecurityConfig `yaml:"security"`
KMS kms.Config `yaml:"kms,omitempty"`
}
// ServerSection holds server-specific configuration
@ -204,5 +206,16 @@ func (c *ServerConfig) Validate() error {
}
}
// Validate KMS configuration if set (defaults to memory provider for development)
if c.KMS.Provider != "" {
if err := c.KMS.Validate(); err != nil {
return fmt.Errorf("invalid KMS configuration: %w", err)
}
} else {
// Default to memory provider for development
c.KMS.Provider = kms.ProviderTypeMemory
c.KMS.Cache = kms.DefaultCacheConfig()
}
return nil
}

File diff suppressed because it is too large Load diff