mirror of
https://github.com/lukaszraczylo/traefik-llmgateway.git
synced 2026-10-17 05:37:43 +00:00
feat: users file with throttled hot reload
Add usersFile (newUsersFile/load) for the JSON users-file shape
{"users":[{name,group,apiKey,limits?}]}, and authStore.maybeReload
for throttled hot reload: checks mtime at most every reloadEvery
(5s), reloads on change, keeps the last good set and logs via
errorf on any error (stat, parse, or a rejected replace), and logs
via logf when the reload changes the file-sourced user count.
nowFn is injectable for tests.
Wire-up in llmgateway.go: newGateway does a synchronous initial
load when Users.File is set (a bad initial file fails the
constructor); ServeHTTP calls g.auth.maybeReload() at entry
(no-op when no file is configured).
Also fixes replaceFileUsers's merge semantics: a file user now
overrides an inline user of the same name, even when their keys
differ, dropping the inline entry from the rebuilt set. The prior
digest-only keying let a same-named inline/file pair with
different keys coexist instead of the file version winning.
This commit is contained in:
+35
-1
@@ -6,7 +6,9 @@ package traefikllmgateway
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
)
|
||||
|
||||
// Config is the plugin's dynamic configuration, populated by Traefik from
|
||||
@@ -125,13 +127,45 @@ func newGateway(ctx context.Context, next http.Handler, config *Config, name str
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Gateway{next: next, name: name, cfg: config, auth: auth}, nil
|
||||
g := &Gateway{next: next, name: name, cfg: config, auth: auth}
|
||||
|
||||
if config.Users != nil && config.Users.File != "" {
|
||||
if err := attachUsersFile(auth, config.Users.File, g); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return g, nil
|
||||
}
|
||||
|
||||
// attachUsersFile performs the synchronous initial load of a Users.File path
|
||||
// and wires auth up for throttled hot reload. A failing initial load is a
|
||||
// constructor error — fail fast on bad config rather than start with an
|
||||
// empty file-sourced user set.
|
||||
func attachUsersFile(auth *authStore, path string, log gatewayLogger) error {
|
||||
uf := newUsersFile(path)
|
||||
initial, err := uf.load()
|
||||
if err != nil {
|
||||
return fmt.Errorf("llmgateway: cannot load initial users file %q: %w", path, err)
|
||||
}
|
||||
if err := auth.replaceFileUsers(initial); err != nil {
|
||||
return fmt.Errorf("llmgateway: initial users file %q: %w", path, err)
|
||||
}
|
||||
|
||||
auth.usersFile = uf
|
||||
auth.log = log
|
||||
if info, statErr := os.Stat(path); statErr == nil {
|
||||
auth.lastModTime = info.ModTime()
|
||||
}
|
||||
auth.lastCheck = auth.nowFn()
|
||||
return nil
|
||||
}
|
||||
|
||||
// ServeHTTP is the internal router. It grows in later tasks.
|
||||
func (g *Gateway) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
sw := &statusTrackingWriter{ResponseWriter: w}
|
||||
defer recoverPanic(sw, g)
|
||||
g.auth.maybeReload()
|
||||
if g.cfg.PassthroughUnknown {
|
||||
g.next.ServeHTTP(sw, r)
|
||||
return
|
||||
|
||||
Reference in New Issue
Block a user