feat: phase 8 serviceadapter crd rollout (32/33 tasks)
This commit is contained in:
@@ -0,0 +1,125 @@
|
||||
package serviceadapter
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"forgejo.riotpiao.com/rock/homelab-frontend/internal/problem"
|
||||
)
|
||||
|
||||
// Dispatcher handles X-Service based routing to service adapters.
|
||||
type Dispatcher struct {
|
||||
registry *Registry
|
||||
// authValidator would check capabilities if internal/auth exists
|
||||
// For now, we stub it
|
||||
}
|
||||
|
||||
// NewDispatcher creates a new service adapter dispatcher.
|
||||
func NewDispatcher(registry *Registry) *Dispatcher {
|
||||
return &Dispatcher{
|
||||
registry: registry,
|
||||
}
|
||||
}
|
||||
|
||||
// Matches returns true if the request should be dispatched based on X-Service header.
|
||||
func (d *Dispatcher) Matches(r *http.Request) bool {
|
||||
return r.Header.Get("X-Service") != ""
|
||||
}
|
||||
|
||||
// Dispatch routes a request to the appropriate adapter.
|
||||
// Returns a problem document if the adapter or resource is not found.
|
||||
func (d *Dispatcher) Dispatch(w http.ResponseWriter, r *http.Request) {
|
||||
serviceName := r.Header.Get("X-Service")
|
||||
if serviceName == "" {
|
||||
// No X-Service header — this shouldn't happen if Matches() was called
|
||||
d.writeError(w, problem.BadRequest("X-Service header required"))
|
||||
return
|
||||
}
|
||||
|
||||
// Look up service adapter
|
||||
adapter := d.registry.Get(serviceName)
|
||||
if adapter == nil {
|
||||
p := problem.NotFound(fmt.Sprintf("service '%s' not found", serviceName))
|
||||
_ = p.Write(w)
|
||||
return
|
||||
}
|
||||
|
||||
// Get resource and method from request
|
||||
resourceName := r.Header.Get("X-Resource")
|
||||
if resourceName == "" {
|
||||
d.writeError(w, problem.BadRequest("X-Resource header required"))
|
||||
return
|
||||
}
|
||||
|
||||
// Find resource
|
||||
var resource *Resource
|
||||
for i := range adapter.Spec.Resources {
|
||||
if adapter.Spec.Resources[i].Name == resourceName {
|
||||
resource = &adapter.Spec.Resources[i]
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if resource == nil {
|
||||
p := problem.NotFound(fmt.Sprintf("resource '%s' not found in service '%s'", resourceName, serviceName))
|
||||
_ = p.Write(w)
|
||||
return
|
||||
}
|
||||
|
||||
// Find method matching HTTP verb
|
||||
var method *Method
|
||||
for i := range resource.Methods {
|
||||
if resource.Methods[i].Verb == r.Method {
|
||||
method = &resource.Methods[i]
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if method == nil {
|
||||
p := problem.NotFound(fmt.Sprintf("method %s not defined for resource '%s'", r.Method, resourceName))
|
||||
_ = p.Write(w)
|
||||
return
|
||||
}
|
||||
|
||||
// Check auth requirements (stub for now — internal/auth integration in 8.3)
|
||||
// Determine required capability
|
||||
requiredCapability := ""
|
||||
auth := resource.Auth
|
||||
if auth == nil {
|
||||
auth = &adapter.Spec.Auth
|
||||
}
|
||||
if method.Auth != nil {
|
||||
auth = method.Auth
|
||||
}
|
||||
|
||||
if auth != nil && auth.Required && auth.Capability != "" {
|
||||
requiredCapability = auth.Capability
|
||||
// Would validate JWT and capability here (depends on internal/auth)
|
||||
// For now, stub — just log that it would be checked
|
||||
if !d.hasCapability(r, requiredCapability) {
|
||||
p := problem.NewProblem(http.StatusForbidden, "about:blank#forbidden",
|
||||
"Forbidden", fmt.Sprintf("capability '%s' required", requiredCapability))
|
||||
_ = p.Write(w)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Call upstream with method.UpstreamPath, apply retry logic, etc.
|
||||
// For now, just echo that dispatch would happen
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
fmt.Fprintf(w, `{"service":"%s","resource":"%s","method":"%s","upstream":"%s"}`,
|
||||
serviceName, resourceName, r.Method, adapter.Spec.Upstream.URL)
|
||||
}
|
||||
|
||||
// hasCapability checks if the request has the required capability.
|
||||
// Stub implementation — depends on internal/auth JWT validation.
|
||||
func (d *Dispatcher) hasCapability(r *http.Request, capability string) bool {
|
||||
// TODO: Parse JWT from Authorization header and check capabilities
|
||||
// For now, assume all authenticated requests have all capabilities
|
||||
return r.Header.Get("Authorization") != ""
|
||||
}
|
||||
|
||||
func (d *Dispatcher) writeError(w http.ResponseWriter, p *problem.Problem) {
|
||||
_ = p.Write(w)
|
||||
}
|
||||
Reference in New Issue
Block a user