mirror of
https://github.com/lukaszraczylo/traefik-llmgateway.git
synced 2026-10-12 04:54:45 +00:00
ci: yaegi catalog-analyzer gate harness
Add tools/yaegi-check, a separate Go module (llmgw-yaegi-check,
never a dependency of the plugin module) that replicates the
Traefik Plugin Catalog analyzer (piceus)'s yaegiMiddlewareCheck:
copy the repo into a temp GOPATH, interp.New + i.Use(stdlib.Symbols),
import the plugin package, eval CreateConfig(), decode the plugin's
real .traefik.yml testData into it via a hand-rolled YAML-subset
parser plus a JSON round-trip, reflect-Call New from outside the
interpreter, then exercise the returned handler with two real HTTP
requests (authenticated GET /v1/models against the testData key, and
the same route unauthenticated). Pins github.com/traefik/yaegi to
v0.16.1, confirmed as Traefik v3.5.0's own vendored version by
downloading its go.mod.
Wire cd tools/yaegi-check && GOWORK=off go run . /Users/nvm/Documents/projects/private/traefik-llmgateway
yaegi-check: OK to run the harness from repo root.
The harness caught a real Yaegi v0.16.1 limitation: calling a struct
FIELD of variadic func type (e.g. modelRegistry.log
func(string, ...any)) with two or more variadic arguments crashes
Yaegi's CFG builder ('index out of range'). The identical call shape
through a method or interface method is unaffected — bisected with a
standalone yaegi repro to confirm the exact trigger before touching
plugin code. Fixed by pre-formatting with fmt.Sprintf at
modelRegistry's three affected call sites (registry.go) so the field
is always called with exactly one variadic argument; documented the
constraint on both modelRegistry.log and limiter.logf so it is not
reintroduced. Verified the gate itself is load-bearing by temporarily
renaming CreateConfig (harness failed non-zero) and restoring it
(harness passed again).
Also fixes two golangci-lint findings in the new harness module
(govet shadow, gosec G301 directory permissions).
This commit is contained in:
+15
-5
@@ -122,8 +122,18 @@ func (st *providerState) finishRefresh(now time.Time, ids []string, err error) {
|
||||
// guarded by its own mutex), so modelRegistry itself needs no lock beyond
|
||||
// warnedMu for the collision-log dedup below.
|
||||
type modelRegistry struct {
|
||||
adapters map[string]providerAdapter
|
||||
states map[string]*providerState
|
||||
adapters map[string]providerAdapter
|
||||
states map[string]*providerState
|
||||
// log is a bound method value (g.errorf), injected rather than holding
|
||||
// a *Gateway directly. Every call site below pre-formats with
|
||||
// fmt.Sprintf and calls log("%s", msg) — never log(format, a, b, ...)
|
||||
// with two or more variadic arguments. Yaegi v0.16.1's CFG builder
|
||||
// panics ("index out of range") compiling a call to a struct FIELD of
|
||||
// variadic func type with 2+ variadic arguments; the identical call
|
||||
// shape through a method or interface method is unaffected — this is
|
||||
// specific to a field holding a func value. Verified empirically
|
||||
// against yaegi v0.16.1 (see tools/yaegi-check) and does not apply to
|
||||
// g.logf/g.errorf, which are methods, not fields.
|
||||
log func(string, ...any)
|
||||
nowFn func() time.Time
|
||||
warned map[string]bool
|
||||
@@ -205,7 +215,7 @@ func (m *modelRegistry) warmFill(ctx context.Context) {
|
||||
cancel()
|
||||
st.finishRefresh(m.now(), ids, err)
|
||||
if err != nil {
|
||||
m.log("model registry: initial discovery for provider %q failed: %v", name, err)
|
||||
m.log("%s", fmt.Sprintf("model registry: initial discovery for provider %q failed: %v", name, err))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -264,7 +274,7 @@ func (m *modelRegistry) refreshProvider(name string, st *providerState, adapter
|
||||
}
|
||||
st.finishRefresh(m.now(), ids, err)
|
||||
if err != nil {
|
||||
m.log("model registry: discovery refresh for provider %q failed: %v", name, err)
|
||||
m.log("%s", fmt.Sprintf("model registry: discovery refresh for provider %q failed: %v", name, err))
|
||||
}
|
||||
}()
|
||||
|
||||
@@ -371,7 +381,7 @@ func (m *modelRegistry) warnCollisionOnce(id, winner string, provs []string) {
|
||||
return
|
||||
}
|
||||
m.warned[id] = true
|
||||
m.log("model registry: model id %q is provided by multiple providers %v; %q wins the bare id", id, provs, winner)
|
||||
m.log("%s", fmt.Sprintf("model registry: model id %q is provided by multiple providers %v; %q wins the bare id", id, provs, winner))
|
||||
}
|
||||
|
||||
// listFor returns grp's visible model catalog as OpenAI-compatible model
|
||||
|
||||
Reference in New Issue
Block a user