Templates with expr-lang (#8450)

* plugin/template: Add expr-lang variables

Signed-off-by: Andri Yngvason <andri@yngvason.is>

* plugin/template: Add extra expressions that must match

Signed-off-by: Andri Yngvason <andri@yngvason.is>

* plugin/template: README: Add embedded device resolution example

Signed-off-by: Andri Yngvason <andri@yngvason.is>

---------

Signed-off-by: Andri Yngvason <andri@yngvason.is>
This commit is contained in:
Andri Yngvason
2026-08-20 03:27:03 +00:00
committed by GitHub
parent a53ecb7c2d
commit a1154dcee5
6 changed files with 982 additions and 0 deletions

View File

@@ -10,9 +10,12 @@ import (
"github.com/coredns/coredns/plugin"
"github.com/coredns/coredns/plugin/metadata"
"github.com/coredns/coredns/plugin/metrics"
"github.com/coredns/coredns/plugin/pkg/expression"
"github.com/coredns/coredns/plugin/pkg/fall"
"github.com/coredns/coredns/request"
"github.com/expr-lang/expr"
"github.com/expr-lang/expr/vm"
"github.com/miekg/dns"
)
@@ -36,6 +39,13 @@ type template struct {
ederror *ederror
fall fall.F
upstream Upstreamer
vars []variable
exprs []*vm.Program
}
type variable struct {
name string
prog *vm.Program
}
type ederror struct {
@@ -59,6 +69,7 @@ type templateData struct {
Message *dns.Msg
Question *dns.Question
Remote string
Var map[string]any
md map[string]metadata.Func
}
@@ -74,6 +85,20 @@ func (data *templateData) Meta(metaName string) string {
return ""
}
func exprEnv(ctx context.Context, state *request.Request, data *templateData) map[string]any {
env := expression.DefaultEnv(ctx, state)
env["group"] = func(name any) string {
switch n := name.(type) {
case int:
return data.Group[strconv.Itoa(n)]
case string:
return data.Group[n]
}
return ""
}
return env
}
// ServeDNS implements the plugin.Handler interface.
func (h Handler) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) {
state := request.Request{W: w, Req: r}
@@ -220,6 +245,30 @@ func (t template) match(ctx context.Context, state request.Request) (*templateDa
}
}
if len(t.vars) > 0 || len(t.exprs) > 0 {
exprState := state // &state would escape unconditionally
env := exprEnv(ctx, &exprState, data)
data.Var = make(map[string]any)
for _, v := range t.vars {
result, err := expr.Run(v.prog, env)
if err != nil {
return data, false, false
}
env[v.name] = result
data.Var[v.name] = result
}
for _, prog := range t.exprs {
result, err := expr.Run(prog, env)
if err != nil {
return data, false, false
}
if b, ok := result.(bool); !ok || !b {
return data, false, t.fall.Through(state.Name())
}
}
}
return data, true, false
}