toolsrv: migrate to closure-capture fsedsl API
Spec() now takes *Server and all handlers are closures that capture it. Removed Ctx type, type aliases, and separate handler functions. The tree structure and behavior are declared together in one place.
This commit is contained in:
parent
f4a6829d89
commit
53b4618561
|
|
@ -11,162 +11,99 @@ import (
|
|||
"ollie/fsedsl"
|
||||
)
|
||||
|
||||
// Ctx is the context passed through the fsedsl tree.
|
||||
type Ctx struct {
|
||||
Server *Server
|
||||
Proc *Proc // set when traversing into /proc/{pid}
|
||||
}
|
||||
|
||||
// Type aliases from fsedsl, specialized to Ctx.
|
||||
type (
|
||||
FsNodeDecl = fsedsl.FsNodeDecl[Ctx]
|
||||
Binding = fsedsl.Binding[Ctx]
|
||||
)
|
||||
|
||||
// Specialized fsedsl constructors for Ctx.
|
||||
var (
|
||||
DirNode = fsedsl.DirNode[Ctx]
|
||||
FileNode = fsedsl.FileNode[Ctx]
|
||||
Each = fsedsl.Each[Ctx]
|
||||
Doc = fsedsl.Doc[Ctx]
|
||||
Read = fsedsl.Read[Ctx]
|
||||
Write = fsedsl.Write[Ctx]
|
||||
BlockOnce = fsedsl.BlockOnce[Ctx]
|
||||
Request = fsedsl.Request[Ctx]
|
||||
)
|
||||
|
||||
// Spec returns the fsedsl specification for the toolsrv namespace.
|
||||
func Spec() FsNodeDecl {
|
||||
return DirNode("/",
|
||||
FileNode("ctl", 0222,
|
||||
Doc("Control: write 'load <tool>' or 'unload <tool>'"),
|
||||
Write(handleCtlWrite),
|
||||
// The server is captured by closures — no context threading needed.
|
||||
func Spec(srv *Server) fsedsl.FsNodeDecl {
|
||||
return fsedsl.DirNode("/",
|
||||
fsedsl.FileNode("ctl", 0222,
|
||||
fsedsl.Doc("Control: write 'load <agentID> <tool>' or 'unload <agentID> <tool>'"),
|
||||
fsedsl.Write(func(data []byte) error {
|
||||
return srv.Fs.HandleCtl(strings.TrimSpace(string(data)))
|
||||
}),
|
||||
),
|
||||
FileNode("tools", 0666,
|
||||
Doc("Tools: write agent ID, read tool list (rdwr)"),
|
||||
Request(handleToolsRequest),
|
||||
fsedsl.FileNode("tools", 0666,
|
||||
fsedsl.Doc("Tools: write agent ID, read tool list (rdwr)"),
|
||||
fsedsl.Request(func(data []byte) ([]byte, error) {
|
||||
agentID := strings.TrimSpace(string(data))
|
||||
if agentID == "" {
|
||||
return nil, fmt.Errorf("agent ID required")
|
||||
}
|
||||
return []byte(srv.Fs.HandleToolsRequest(agentID)), nil
|
||||
}),
|
||||
),
|
||||
FileNode("info", 0444,
|
||||
Doc("Host info: platform, arch"),
|
||||
Read(handleInfoRead),
|
||||
fsedsl.FileNode("info", 0444,
|
||||
fsedsl.Doc("Host info: platform, arch"),
|
||||
fsedsl.Read(func() ([]byte, error) {
|
||||
return []byte(srv.HostInfo()), nil
|
||||
}),
|
||||
),
|
||||
DirNode("proc",
|
||||
FileNode("new", 0666,
|
||||
Doc("Execute tool: write token + tool + args, read result (blocking)"),
|
||||
Request(handleProcNew),
|
||||
fsedsl.DirNode("proc",
|
||||
fsedsl.FileNode("new", 0666,
|
||||
fsedsl.Doc("Execute tool: write token + tool + args, read result (blocking)"),
|
||||
fsedsl.Request(func(data []byte) ([]byte, error) {
|
||||
result, err := srv.Fs.HandleProcNew(context.Background(), string(data))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return []byte(result), nil
|
||||
}),
|
||||
),
|
||||
FileNode("new.bg", 0666,
|
||||
Doc("Execute tool in background: write token + tool + args, read pid"),
|
||||
Request(handleProcNewBg),
|
||||
),
|
||||
Each("{pid}", procBindings,
|
||||
FileNode("out", 0444,
|
||||
Doc("Process output"),
|
||||
Read(handleProcOutRead),
|
||||
),
|
||||
FileNode("wait", 0444,
|
||||
Doc("Block until process exits, returns exit code"),
|
||||
BlockOnce(handleProcWait),
|
||||
),
|
||||
FileNode("stat", 0444,
|
||||
Doc("Process status: running/exited, runtime, tool"),
|
||||
Read(handleProcStatRead),
|
||||
),
|
||||
FileNode("ctl", 0222,
|
||||
Doc("Process control: write 'signal <N>' or 'dismiss'"),
|
||||
Write(handleProcCtlWrite),
|
||||
),
|
||||
fsedsl.FileNode("new.bg", 0666,
|
||||
fsedsl.Doc("Execute tool in background: write token + tool + args, read pid"),
|
||||
fsedsl.Request(func(data []byte) ([]byte, error) {
|
||||
result, err := srv.Fs.HandleProcNewBg(context.Background(), string(data))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return []byte(result), nil
|
||||
}),
|
||||
),
|
||||
fsedsl.Each("{pid}", func() ([]fsedsl.Binding, error) {
|
||||
pids := srv.Fs.ListProcs()
|
||||
var bindings []fsedsl.Binding
|
||||
for _, pid := range pids {
|
||||
proc := srv.Fs.GetProc(pid)
|
||||
if proc == nil {
|
||||
continue
|
||||
}
|
||||
p := proc
|
||||
bindings = append(bindings, fsedsl.Binding{
|
||||
Name: strconv.Itoa(pid),
|
||||
Children: []fsedsl.FsNodeDecl{
|
||||
fsedsl.FileNode("out", 0444,
|
||||
fsedsl.Doc("Process output"),
|
||||
fsedsl.Read(func() ([]byte, error) {
|
||||
return []byte(p.Output()), nil
|
||||
}),
|
||||
),
|
||||
fsedsl.FileNode("wait", 0444,
|
||||
fsedsl.Doc("Block until process exits, returns exit code"),
|
||||
fsedsl.BlockOnce(func(_ context.Context, _ string) ([]byte, string, error) {
|
||||
exitCode := p.Wait()
|
||||
return []byte(fmt.Sprintf("%d\n", exitCode)), "", nil
|
||||
}),
|
||||
),
|
||||
fsedsl.FileNode("stat", 0444,
|
||||
fsedsl.Doc("Process status: running/exited, runtime, tool"),
|
||||
fsedsl.Read(func() ([]byte, error) {
|
||||
return []byte(p.Stat()), nil
|
||||
}),
|
||||
),
|
||||
fsedsl.FileNode("ctl", 0222,
|
||||
fsedsl.Doc("Process control: write 'signal <N>' or 'dismiss'"),
|
||||
fsedsl.Write(func(data []byte) error {
|
||||
return srv.Fs.HandleProcCtl(p.ID, strings.TrimSpace(string(data)))
|
||||
}),
|
||||
),
|
||||
},
|
||||
})
|
||||
}
|
||||
return bindings, nil
|
||||
}),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
// --- Handlers ---
|
||||
|
||||
func handleCtlWrite(ctx Ctx, data []byte) error {
|
||||
return ctx.Server.Fs.HandleCtl(strings.TrimSpace(string(data)))
|
||||
}
|
||||
|
||||
func handleToolsRequest(ctx Ctx, data []byte) ([]byte, error) {
|
||||
agentID := strings.TrimSpace(string(data))
|
||||
if agentID == "" {
|
||||
return nil, fmt.Errorf("agent ID required")
|
||||
}
|
||||
return []byte(ctx.Server.Fs.HandleToolsRequest(agentID)), nil
|
||||
}
|
||||
|
||||
func handleInfoRead(ctx Ctx) ([]byte, error) {
|
||||
return []byte(ctx.Server.HostInfo()), nil
|
||||
}
|
||||
|
||||
func handleProcNew(ctx Ctx, data []byte) ([]byte, error) {
|
||||
result, err := ctx.Server.Fs.HandleProcNew(context.Background(), string(data))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return []byte(result), nil
|
||||
}
|
||||
|
||||
func handleProcNewBg(ctx Ctx, data []byte) ([]byte, error) {
|
||||
result, err := ctx.Server.Fs.HandleProcNewBg(context.Background(), string(data))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return []byte(result), nil
|
||||
}
|
||||
|
||||
// procBindings returns bindings for each running process.
|
||||
func procBindings(ctx Ctx) ([]Binding, error) {
|
||||
pids := ctx.Server.Fs.ListProcs()
|
||||
var bindings []Binding
|
||||
|
||||
for _, pid := range pids {
|
||||
proc := ctx.Server.Fs.GetProc(pid)
|
||||
if proc == nil {
|
||||
continue
|
||||
}
|
||||
p := proc // capture for closure
|
||||
bindings = append(bindings, Binding{
|
||||
Name: strconv.Itoa(pid),
|
||||
Applier: func(c Ctx) Ctx {
|
||||
c.Proc = p
|
||||
return c
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
return bindings, nil
|
||||
}
|
||||
|
||||
func handleProcOutRead(ctx Ctx) ([]byte, error) {
|
||||
if ctx.Proc == nil {
|
||||
return nil, fmt.Errorf("no process context")
|
||||
}
|
||||
return []byte(ctx.Proc.Output()), nil
|
||||
}
|
||||
|
||||
func handleProcWait(ctx Ctx, cctx context.Context, base string) ([]byte, string, error) {
|
||||
if ctx.Proc == nil {
|
||||
return nil, "", fmt.Errorf("no process context")
|
||||
}
|
||||
exitCode := ctx.Proc.Wait()
|
||||
return []byte(fmt.Sprintf("%d\n", exitCode)), "", nil
|
||||
}
|
||||
|
||||
func handleProcStatRead(ctx Ctx) ([]byte, error) {
|
||||
if ctx.Proc == nil {
|
||||
return nil, fmt.Errorf("no process context")
|
||||
}
|
||||
return []byte(ctx.Proc.Stat()), nil
|
||||
}
|
||||
|
||||
func handleProcCtlWrite(ctx Ctx, data []byte) error {
|
||||
if ctx.Proc == nil {
|
||||
return fmt.Errorf("no process context")
|
||||
}
|
||||
return ctx.Server.Fs.HandleProcCtl(ctx.Proc.ID, strings.TrimSpace(string(data)))
|
||||
}
|
||||
|
||||
// Mode constants for convenience.
|
||||
const (
|
||||
ModeReadOnly os.FileMode = 0444
|
||||
|
|
|
|||
|
|
@ -84,8 +84,7 @@ func (s *Server) HostInfo() string {
|
|||
|
||||
// BuildTree creates the fsedsl tree for this server.
|
||||
func (s *Server) BuildTree() *fsedsl.Tree {
|
||||
ctx := Ctx{Server: s}
|
||||
return fsedsl.BuildTree(Spec(), ctx)
|
||||
return fsedsl.BuildTree(Spec(s))
|
||||
}
|
||||
|
||||
// GenerateSecret generates a random shared secret for toolsrv auth.
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ import (
|
|||
"ollie/cmd/toolsrv/internal/fs"
|
||||
"ollie/cmd/toolsrv/internal/registry"
|
||||
"ollie/env"
|
||||
"ollie/fsedsl"
|
||||
|
||||
olog "ollie/log"
|
||||
"ollie/paths"
|
||||
)
|
||||
|
|
@ -88,8 +88,7 @@ func main() {
|
|||
}
|
||||
|
||||
// Build the filesystem tree
|
||||
ctx := fs.Ctx{Server: srv}
|
||||
tree := fsedsl.BuildTree(fs.Spec(), ctx)
|
||||
tree := srv.BuildTree()
|
||||
if tree == nil {
|
||||
logger.Error("failed to build filesystem tree")
|
||||
os.Exit(1)
|
||||
|
|
|
|||
Loading…
Reference in New Issue