aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar John Belamaric <jbelamaric@google.com> 2019-07-03 16:10:56 +0100
committerGravatar GitHub <noreply@github.com> 2019-07-03 16:10:56 +0100
commit7cf73cc01db2bfebf7a5b44901e71a894811e203 (patch)
tree490c1e1dd34a1e3e2f1ffbb6ea587e2a4abfd9ac
parent2faad5b3976b361c5bd357bc6d14537fe6ae1400 (diff)
downloadcoredns-7cf73cc01db2bfebf7a5b44901e71a894811e203.tar.gz
coredns-7cf73cc01db2bfebf7a5b44901e71a894811e203.tar.zst
coredns-7cf73cc01db2bfebf7a5b44901e71a894811e203.zip
plugin/template: support metadata (#2958)
* Enable use of metadata in templates * Update README * Don't stash away ctx, instead use a new func
-rw-r--r--plugin/metadata/provider.go10
-rw-r--r--plugin/template/README.md2
-rw-r--r--plugin/template/template.go22
-rw-r--r--plugin/template/template_test.go105
4 files changed, 135 insertions, 4 deletions
diff --git a/plugin/metadata/provider.go b/plugin/metadata/provider.go
index b22064200..309d304b7 100644
--- a/plugin/metadata/provider.go
+++ b/plugin/metadata/provider.go
@@ -82,6 +82,16 @@ func Labels(ctx context.Context) []string {
return nil
}
+// ValueFuncs returns the map[string]Func from the context, or nil if it does not exist.
+func ValueFuncs(ctx context.Context) map[string]Func {
+ if metadata := ctx.Value(key{}); metadata != nil {
+ if m, ok := metadata.(md); ok {
+ return m
+ }
+ }
+ return nil
+}
+
// ValueFunc returns the value function of label. If none can be found nil is returned. Calling the
// function returns the value of the label.
func ValueFunc(ctx context.Context, label string) Func {
diff --git a/plugin/template/README.md b/plugin/template/README.md
index 993fec8b7..64af37f93 100644
--- a/plugin/template/README.md
+++ b/plugin/template/README.md
@@ -48,6 +48,8 @@ Each resource record is a full-featured [Go template](https://golang.org/pkg/tex
* `.Group` a map of the named capture groups.
* `.Message` the complete incoming DNS message.
* `.Question` the matched question section.
+* `.Meta` a function that takes a metadata name and returns the value, if the
+ metadata plugin is enabled. For example, `.Meta "kubernetes/client-namespace"`
The output of the template must be a [RFC 1035](https://tools.ietf.org/html/rfc1035) style resource record (commonly referred to as a "zone file").
diff --git a/plugin/template/template.go b/plugin/template/template.go
index 1c9f61622..eaa291f7f 100644
--- a/plugin/template/template.go
+++ b/plugin/template/template.go
@@ -8,6 +8,7 @@ import (
gotmpl "text/template"
"github.com/coredns/coredns/plugin"
+ "github.com/coredns/coredns/plugin/metadata"
"github.com/coredns/coredns/plugin/metrics"
"github.com/coredns/coredns/plugin/pkg/fall"
"github.com/coredns/coredns/plugin/pkg/upstream"
@@ -47,6 +48,19 @@ type templateData struct {
Type string
Message *dns.Msg
Question *dns.Question
+ md map[string]metadata.Func
+}
+
+func (data *templateData) Meta(metaName string) string {
+ if data.md == nil {
+ return ""
+ }
+
+ if f, ok := data.md[metaName]; ok {
+ return f()
+ }
+
+ return ""
}
// ServeDNS implements the plugin.Handler interface.
@@ -59,7 +73,7 @@ func (h Handler) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg)
}
for _, template := range h.Templates {
- data, match, fthrough := template.match(state, zone)
+ data, match, fthrough := template.match(ctx, state, zone)
if !match {
if !fthrough {
return dns.RcodeNameError, nil
@@ -114,7 +128,7 @@ func (h Handler) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg)
// Name implements the plugin.Handler interface.
func (h Handler) Name() string { return "template" }
-func executeRRTemplate(server, section string, template *gotmpl.Template, data templateData) (dns.RR, error) {
+func executeRRTemplate(server, section string, template *gotmpl.Template, data *templateData) (dns.RR, error) {
buffer := &bytes.Buffer{}
err := template.Execute(buffer, data)
if err != nil {
@@ -129,9 +143,9 @@ func executeRRTemplate(server, section string, template *gotmpl.Template, data t
return rr, nil
}
-func (t template) match(state request.Request, zone string) (templateData, bool, bool) {
+func (t template) match(ctx context.Context, state request.Request, zone string) (*templateData, bool, bool) {
q := state.Req.Question[0]
- data := templateData{}
+ data := &templateData{md: metadata.ValueFuncs(ctx)}
zone = plugin.Zones(t.zones).Matches(state.Name())
if zone == "" {
diff --git a/plugin/template/template_test.go b/plugin/template/template_test.go
index d2869b53b..1aa26229f 100644
--- a/plugin/template/template_test.go
+++ b/plugin/template/template_test.go
@@ -7,6 +7,7 @@ import (
"testing"
gotmpl "text/template"
+ "github.com/coredns/coredns/plugin/metadata"
"github.com/coredns/coredns/plugin/pkg/dnstest"
"github.com/coredns/coredns/plugin/pkg/fall"
"github.com/coredns/coredns/plugin/test"
@@ -100,6 +101,24 @@ func TestHandler(t *testing.T) {
fall: fall.Root,
zones: []string{"."},
}
+ mdTemplate := template{
+ regex: []*regexp.Regexp{regexp.MustCompile("(^|[.])ip-10-(?P<b>[0-9]*)-(?P<c>[0-9]*)-(?P<d>[0-9]*)[.]example[.]$")},
+ answer: []*gotmpl.Template{gotmpl.Must(gotmpl.New("answer").Parse(`{{ .Meta "foo" }}-{{ .Name }} 60 IN A 10.{{ .Group.b }}.{{ .Group.c }}.{{ .Group.d }}`))},
+ additional: []*gotmpl.Template{gotmpl.Must(gotmpl.New("additional").Parse(`{{ .Meta "bar" }}.example. IN A 203.0.113.8`))},
+ authority: []*gotmpl.Template{gotmpl.Must(gotmpl.New("authority").Parse(`example. IN NS {{ .Meta "bar" }}.example.com.`))},
+ qclass: dns.ClassANY,
+ qtype: dns.TypeANY,
+ fall: fall.Root,
+ zones: []string{"."},
+ }
+ mdMissingTemplate := template{
+ regex: []*regexp.Regexp{regexp.MustCompile("(^|[.])ip-10-(?P<b>[0-9]*)-(?P<c>[0-9]*)-(?P<d>[0-9]*)[.]example[.]$")},
+ answer: []*gotmpl.Template{gotmpl.Must(gotmpl.New("answer").Parse(`{{ .Meta "foofoo" }}{{ .Name }} 60 IN A 10.{{ .Group.b }}.{{ .Group.c }}.{{ .Group.d }}`))},
+ qclass: dns.ClassANY,
+ qtype: dns.TypeANY,
+ fall: fall.Root,
+ zones: []string{"."},
+ }
tests := []struct {
tmpl template
@@ -110,6 +129,7 @@ func TestHandler(t *testing.T) {
expectedCode int
expectedErr string
verifyResponse func(*dns.Msg) error
+ md map[string]string
}{
{
name: "RcodeServFail",
@@ -276,6 +296,78 @@ func TestHandler(t *testing.T) {
return nil
},
},
+ {
+ name: "mdMatch",
+ tmpl: mdTemplate,
+ qclass: dns.ClassINET,
+ qtype: dns.TypeA,
+ qname: "ip-10-95-12-8.example.",
+ verifyResponse: func(r *dns.Msg) error {
+ if len(r.Answer) != 1 {
+ return fmt.Errorf("expected 1 answer, got %v", len(r.Answer))
+ }
+ if r.Answer[0].Header().Rrtype != dns.TypeA {
+ return fmt.Errorf("expected an A record answer, got %v", dns.TypeToString[r.Answer[0].Header().Rrtype])
+ }
+ name := "myfoo-ip-10-95-12-8.example."
+ if r.Answer[0].Header().Name != name {
+ return fmt.Errorf("expected answer name %q, got %q", name, r.Answer[0].Header().Name)
+ }
+ if len(r.Extra) != 1 {
+ return fmt.Errorf("expected 1 extra record, got %v", len(r.Extra))
+ }
+ if r.Extra[0].Header().Rrtype != dns.TypeA {
+ return fmt.Errorf("expected an additional A record, got %v", dns.TypeToString[r.Extra[0].Header().Rrtype])
+ }
+ name = "mybar.example."
+ if r.Extra[0].Header().Name != name {
+ return fmt.Errorf("expected additional name %q, got %q", name, r.Extra[0].Header().Name)
+ }
+ if len(r.Ns) != 1 {
+ return fmt.Errorf("expected 1 authoritative record, got %v", len(r.Extra))
+ }
+ if r.Ns[0].Header().Rrtype != dns.TypeNS {
+ return fmt.Errorf("expected an authoritative NS record, got %v", dns.TypeToString[r.Extra[0].Header().Rrtype])
+ }
+ ns, ok := r.Ns[0].(*dns.NS)
+ if !ok {
+ return fmt.Errorf("expected NS record to be type NS, got %v", r.Ns[0])
+ }
+ rdata := "mybar.example.com."
+ if ns.Ns != rdata {
+ return fmt.Errorf("expected ns rdata %q, got %q", rdata, ns.Ns)
+ }
+ return nil
+ },
+ md: map[string]string{
+ "foo": "myfoo",
+ "bar": "mybar",
+ "foobar": "myfoobar",
+ },
+ },
+ {
+ name: "mdMissing",
+ tmpl: mdMissingTemplate,
+ qclass: dns.ClassINET,
+ qtype: dns.TypeA,
+ qname: "ip-10-95-12-8.example.",
+ verifyResponse: func(r *dns.Msg) error {
+ if len(r.Answer) != 1 {
+ return fmt.Errorf("expected 1 answer, got %v", len(r.Answer))
+ }
+ if r.Answer[0].Header().Rrtype != dns.TypeA {
+ return fmt.Errorf("expected an A record answer, got %v", dns.TypeToString[r.Answer[0].Header().Rrtype])
+ }
+ name := "ip-10-95-12-8.example."
+ if r.Answer[0].Header().Name != name {
+ return fmt.Errorf("expected answer name %q, got %q", name, r.Answer[0].Header().Name)
+ }
+ return nil
+ },
+ md: map[string]string{
+ "foo": "myfoo",
+ },
+ },
}
ctx := context.TODO()
@@ -294,6 +386,19 @@ func TestHandler(t *testing.T) {
}},
}
rec := dnstest.NewRecorder(&test.ResponseWriter{})
+ if tr.md != nil {
+ ctx = metadata.ContextWithMetadata(context.Background())
+
+ for k, v := range tr.md {
+ // Go requires copying to a local variable for the closure to work
+ kk := k
+ vv := v
+ metadata.SetValueFunc(ctx, kk, func() string {
+ return vv
+ })
+ }
+ }
+
code, err := handler.ServeDNS(ctx, rec, req)
if err == nil && tr.expectedErr != "" {
t.Errorf("Test %v expected error: %v, got nothing", tr.name, tr.expectedErr)