aboutsummaryrefslogtreecommitdiff
path: root/middleware
diff options
context:
space:
mode:
authorGravatar Miek Gieben <miek@miek.nl> 2016-08-19 17:14:17 -0700
committerGravatar GitHub <noreply@github.com> 2016-08-19 17:14:17 -0700
commit9ac3cab1b7b1b1e78f86ce3c6a80fbee312162e6 (patch)
tree437e9755927c33af16276ad2602a6da115f948cb /middleware
parenta1989c35231b0e5ea271b2f68d82c1a63e697cd0 (diff)
downloadcoredns-9ac3cab1b7b1b1e78f86ce3c6a80fbee312162e6.tar.gz
coredns-9ac3cab1b7b1b1e78f86ce3c6a80fbee312162e6.tar.zst
coredns-9ac3cab1b7b1b1e78f86ce3c6a80fbee312162e6.zip
Make CoreDNS a server type plugin for Caddy (#220)
* Make CoreDNS a server type plugin for Caddy Remove code we don't need and port all middleware over. Fix all tests and rework the documentation. Also make `go generate` build a caddy binary which we then copy into our directory. This means `go build`-builds remain working as-is. And new etc instances in each etcd test for better isolation. Fix more tests and rework test.Server with the newer support Caddy offers. Fix Makefile to support new mode of operation.
Diffstat (limited to 'middleware')
-rw-r--r--middleware/bind/README.md21
-rw-r--r--middleware/bind/bind.go10
-rw-r--r--middleware/bind/bind_test.go30
-rw-r--r--middleware/bind/setup.go23
-rw-r--r--middleware/cache/setup.go66
-rw-r--r--middleware/chaos/setup.go50
-rw-r--r--middleware/chaos/setup_test.go63
-rw-r--r--middleware/dnssec/cache_test.go2
-rw-r--r--middleware/dnssec/dnssec.go5
-rw-r--r--middleware/dnssec/dnssec_test.go4
-rw-r--r--middleware/dnssec/handler_test.go4
-rw-r--r--middleware/dnssec/setup.go91
-rw-r--r--middleware/dnssec/setup_test.go56
-rw-r--r--middleware/errors/setup.go133
-rw-r--r--middleware/errors/setup_test.go98
-rw-r--r--middleware/etcd/cname_test.go2
-rw-r--r--middleware/etcd/debug_test.go7
-rw-r--r--middleware/etcd/group_test.go2
-rw-r--r--middleware/etcd/multi_test.go3
-rw-r--r--middleware/etcd/other_test.go2
-rw-r--r--middleware/etcd/proxy_lookup_test.go11
-rw-r--r--middleware/etcd/setup.go207
-rw-r--r--middleware/etcd/setup_test.go17
-rw-r--r--middleware/etcd/stub_test.go3
-rw-r--r--middleware/file/setup.go143
-rw-r--r--middleware/health/health.go7
-rw-r--r--middleware/health/setup.go42
-rw-r--r--middleware/kubernetes/controller.go56
-rw-r--r--middleware/kubernetes/handler.go3
-rw-r--r--middleware/kubernetes/kubernetes.go48
-rw-r--r--middleware/kubernetes/nametemplate/nametemplate.go5
-rw-r--r--middleware/kubernetes/setup.go140
-rw-r--r--middleware/kubernetes/setup_test.go390
-rw-r--r--middleware/loadbalance/setup.go25
-rw-r--r--middleware/log/setup.go141
-rw-r--r--middleware/log/setup_test.go137
-rw-r--r--middleware/metrics/metrics.go2
-rw-r--r--middleware/metrics/setup.go84
-rw-r--r--middleware/pprof/pprof.go2
-rw-r--r--middleware/pprof/setup.go40
-rw-r--r--middleware/pprof/setup_test.go32
-rw-r--r--middleware/proxy/setup.go26
-rw-r--r--middleware/proxy/upstream.go9
-rw-r--r--middleware/rewrite/setup.go115
-rw-r--r--middleware/roller.go35
-rw-r--r--middleware/secondary/setup.go82
46 files changed, 2362 insertions, 112 deletions
diff --git a/middleware/bind/README.md b/middleware/bind/README.md
new file mode 100644
index 000000000..ad23b6153
--- /dev/null
+++ b/middleware/bind/README.md
@@ -0,0 +1,21 @@
+# bind
+
+bind overrides the host to which the server should bind. Normally, the listener binds to the
+wildcard host. However, you may force the listener to bind to another IP instead. This
+directive accepts only an address, not a port.
+
+## Syntax
+
+~~~ txt
+bind address
+~~~
+
+address is the IP address to bind to.
+
+## Examples
+
+To make your socket accessible only to that machine, bind to IP 127.0.0.1 (localhost):
+
+~~~ txt
+bind 127.0.0.1
+~~~
diff --git a/middleware/bind/bind.go b/middleware/bind/bind.go
new file mode 100644
index 000000000..ac27c993b
--- /dev/null
+++ b/middleware/bind/bind.go
@@ -0,0 +1,10 @@
+package bind
+
+import "github.com/mholt/caddy"
+
+func init() {
+ caddy.RegisterPlugin("bind", caddy.Plugin{
+ ServerType: "dns",
+ Action: setupBind,
+ })
+}
diff --git a/middleware/bind/bind_test.go b/middleware/bind/bind_test.go
new file mode 100644
index 000000000..d61741a02
--- /dev/null
+++ b/middleware/bind/bind_test.go
@@ -0,0 +1,30 @@
+package bind
+
+import (
+ "testing"
+
+ "github.com/miekg/coredns/core/dnsserver"
+
+ "github.com/mholt/caddy"
+)
+
+func TestSetupBind(t *testing.T) {
+ c := caddy.NewTestController("dns", `bind 1.2.3.4`)
+ err := setupBind(c)
+ if err != nil {
+ t.Fatalf("Expected no errors, but got: %v", err)
+ }
+
+ cfg := dnsserver.GetConfig(c)
+ if got, want := cfg.ListenHost, "1.2.3.4"; got != want {
+ t.Errorf("Expected the config's ListenHost to be %s, was %s", want, got)
+ }
+}
+
+func TestBindAddress(t *testing.T) {
+ c := caddy.NewTestController("dns", `bind 1.2.3.bla`)
+ err := setupBind(c)
+ if err == nil {
+ t.Fatalf("Expected errors, but got none")
+ }
+}
diff --git a/middleware/bind/setup.go b/middleware/bind/setup.go
new file mode 100644
index 000000000..c08098b5d
--- /dev/null
+++ b/middleware/bind/setup.go
@@ -0,0 +1,23 @@
+package bind
+
+import (
+ "fmt"
+ "net"
+
+ "github.com/miekg/coredns/core/dnsserver"
+
+ "github.com/mholt/caddy"
+)
+
+func setupBind(c *caddy.Controller) error {
+ config := dnsserver.GetConfig(c)
+ for c.Next() {
+ if !c.Args(&config.ListenHost) {
+ return c.ArgErr()
+ }
+ }
+ if net.ParseIP(config.ListenHost) == nil {
+ return fmt.Errorf("not a valid IP address: %s", config.ListenHost)
+ }
+ return nil
+}
diff --git a/middleware/cache/setup.go b/middleware/cache/setup.go
new file mode 100644
index 000000000..ab7f423f2
--- /dev/null
+++ b/middleware/cache/setup.go
@@ -0,0 +1,66 @@
+package cache
+
+import (
+ "strconv"
+
+ "github.com/miekg/coredns/core/dnsserver"
+ "github.com/miekg/coredns/middleware"
+
+ "github.com/mholt/caddy"
+)
+
+func init() {
+ caddy.RegisterPlugin("cache", caddy.Plugin{
+ ServerType: "dns",
+ Action: setup,
+ })
+}
+
+// Cache sets up the root file path of the server.
+func setup(c *caddy.Controller) error {
+ ttl, zones, err := cacheParse(c)
+ if err != nil {
+ return err
+ }
+ dnsserver.GetConfig(c).AddMiddleware(func(next dnsserver.Handler) dnsserver.Handler {
+ return NewCache(ttl, zones, next)
+ })
+
+ return nil
+}
+
+func cacheParse(c *caddy.Controller) (int, []string, error) {
+ var (
+ err error
+ ttl int
+ origins []string
+ )
+
+ for c.Next() {
+ if c.Val() == "cache" {
+ // cache [ttl] [zones..]
+ origins = make([]string, len(c.ServerBlockKeys))
+ copy(origins, c.ServerBlockKeys)
+ args := c.RemainingArgs()
+ if len(args) > 0 {
+ origins = args
+ // first args may be just a number, then it is the ttl, if not it is a zone
+ t := origins[0]
+ ttl, err = strconv.Atoi(t)
+ if err == nil {
+ origins = origins[1:]
+ if len(origins) == 0 {
+ // There was *only* the ttl, revert back to server block
+ copy(origins, c.ServerBlockKeys)
+ }
+ }
+ }
+
+ for i, _ := range origins {
+ origins[i] = middleware.Host(origins[i]).Normalize()
+ }
+ return ttl, origins, nil
+ }
+ }
+ return 0, nil, nil
+}
diff --git a/middleware/chaos/setup.go b/middleware/chaos/setup.go
new file mode 100644
index 000000000..8bdb3053e
--- /dev/null
+++ b/middleware/chaos/setup.go
@@ -0,0 +1,50 @@
+package chaos
+
+import (
+ "github.com/miekg/coredns/core/dnsserver"
+
+ "github.com/mholt/caddy"
+)
+
+func init() {
+ caddy.RegisterPlugin("chaos", caddy.Plugin{
+ ServerType: "dns",
+ Action: setup,
+ })
+}
+
+func setup(c *caddy.Controller) error {
+ version, authors, err := chaosParse(c)
+ if err != nil {
+ return err
+ }
+
+ dnsserver.GetConfig(c).AddMiddleware(func(next dnsserver.Handler) dnsserver.Handler {
+ return Chaos{Next: next, Version: version, Authors: authors}
+ })
+
+ return nil
+}
+
+func chaosParse(c *caddy.Controller) (string, map[string]bool, error) {
+ version := ""
+ authors := make(map[string]bool)
+
+ for c.Next() {
+ args := c.RemainingArgs()
+ if len(args) == 0 {
+ return defaultVersion, nil, nil
+ }
+ if len(args) == 1 {
+ return args[0], nil, nil
+ }
+ version = args[0]
+ for _, a := range args[1:] {
+ authors[a] = true
+ }
+ return version, authors, nil
+ }
+ return version, authors, nil
+}
+
+const defaultVersion = "CoreDNS"
diff --git a/middleware/chaos/setup_test.go b/middleware/chaos/setup_test.go
new file mode 100644
index 000000000..c1741cdf6
--- /dev/null
+++ b/middleware/chaos/setup_test.go
@@ -0,0 +1,63 @@
+package chaos
+
+import (
+ "fmt"
+ "strings"
+ "testing"
+
+ "github.com/mholt/caddy"
+)
+
+func TestSetupChaos(t *testing.T) {
+ tests := []struct {
+ input string
+ shouldErr bool
+ expectedVersion string // expected version.
+ expectedAuthor string // expected author (string, although we get a map).
+ expectedErrContent string // substring from the expected error. Empty for positive cases.
+ }{
+ // positive
+ {
+ `chaos`, false, defaultVersion, "", "",
+ },
+ {
+ `chaos v2`, false, "v2", "", "",
+ },
+ {
+ `chaos v3 "Miek Gieben"`, false, "v3", "Miek Gieben", "",
+ },
+ {
+ fmt.Sprintf(`chaos {
+ %s
+ }`, defaultVersion), false, defaultVersion, "", "",
+ },
+ }
+
+ for i, test := range tests {
+ c := caddy.NewTestController("dns", test.input)
+ version, authors, err := chaosParse(c)
+
+ if test.shouldErr && err == nil {
+ t.Errorf("Test %d: Expected error but found %s for input %s", i, err, test.input)
+ }
+
+ if err != nil {
+ if !test.shouldErr {
+ t.Errorf("Test %d: Expected no error but found one for input %s. Error was: %v", i, test.input, err)
+ }
+
+ if !strings.Contains(err.Error(), test.expectedErrContent) {
+ t.Errorf("Test %d: Expected error to contain: %v, found error: %v, input: %s", i, test.expectedErrContent, err, test.input)
+ }
+ }
+
+ if !test.shouldErr && version != test.expectedVersion {
+ t.Errorf("Chaos not correctly set for input %s. Expected: %s, actual: %s", test.input, test.expectedVersion, version)
+ }
+ if !test.shouldErr && authors != nil {
+ if _, ok := authors[test.expectedAuthor]; !ok {
+ t.Errorf("Chaos not correctly set for input %s. Expected: '%s', actual: '%s'", test.input, test.expectedAuthor, "Miek Gieben")
+ }
+ }
+ }
+}
diff --git a/middleware/dnssec/cache_test.go b/middleware/dnssec/cache_test.go
index 0039586d5..3062f99b0 100644
--- a/middleware/dnssec/cache_test.go
+++ b/middleware/dnssec/cache_test.go
@@ -22,7 +22,7 @@ func TestCacheSet(t *testing.T) {
m := testMsg()
state := middleware.State{Req: m}
k := key(m.Answer) // calculate *before* we add the sig
- d := NewDnssec([]string{"miek.nl."}, []*DNSKEY{dnskey}, nil)
+ d := New([]string{"miek.nl."}, []*DNSKEY{dnskey}, nil)
m = d.Sign(state, "miek.nl.", time.Now().UTC())
_, ok := d.get(k)
diff --git a/middleware/dnssec/dnssec.go b/middleware/dnssec/dnssec.go
index 4907f1621..f517bfe2c 100644
--- a/middleware/dnssec/dnssec.go
+++ b/middleware/dnssec/dnssec.go
@@ -11,14 +11,15 @@ import (
)
type Dnssec struct {
- Next middleware.Handler
+ Next middleware.Handler
+
zones []string
keys []*DNSKEY
inflight *singleflight.Group
cache *gcache.Cache
}
-func NewDnssec(zones []string, keys []*DNSKEY, next middleware.Handler) Dnssec {
+func New(zones []string, keys []*DNSKEY, next middleware.Handler) Dnssec {
return Dnssec{Next: next,
zones: zones,
keys: keys,
diff --git a/middleware/dnssec/dnssec_test.go b/middleware/dnssec/dnssec_test.go
index 49b0d5d3a..10f731325 100644
--- a/middleware/dnssec/dnssec_test.go
+++ b/middleware/dnssec/dnssec_test.go
@@ -69,7 +69,7 @@ func TestSigningDifferentZone(t *testing.T) {
m := testMsgEx()
state := middleware.State{Req: m}
- d := NewDnssec([]string{"example.org."}, []*DNSKEY{key}, nil)
+ d := New([]string{"example.org."}, []*DNSKEY{key}, nil)
m = d.Sign(state, "example.org.", time.Now().UTC())
if !section(m.Answer, 1) {
t.Errorf("answer section should have 1 sig")
@@ -158,7 +158,7 @@ func testDelegationMsg() *dns.Msg {
func newDnssec(t *testing.T, zones []string) (Dnssec, func(), func()) {
k, rm1, rm2 := newKey(t)
- d := NewDnssec(zones, []*DNSKEY{k}, nil)
+ d := New(zones, []*DNSKEY{k}, nil)
return d, rm1, rm2
}
diff --git a/middleware/dnssec/handler_test.go b/middleware/dnssec/handler_test.go
index 6f537b90e..f7cb7e680 100644
--- a/middleware/dnssec/handler_test.go
+++ b/middleware/dnssec/handler_test.go
@@ -77,7 +77,7 @@ func TestLookupZone(t *testing.T) {
dnskey, rm1, rm2 := newKey(t)
defer rm1()
defer rm2()
- dh := NewDnssec([]string{"miek.nl."}, []*DNSKEY{dnskey}, fm)
+ dh := New([]string{"miek.nl."}, []*DNSKEY{dnskey}, fm)
ctx := context.TODO()
for _, tc := range dnsTestCases {
@@ -115,7 +115,7 @@ func TestLookupDNSKEY(t *testing.T) {
dnskey, rm1, rm2 := newKey(t)
defer rm1()
defer rm2()
- dh := NewDnssec([]string{"miek.nl."}, []*DNSKEY{dnskey}, test.ErrorHandler())
+ dh := New([]string{"miek.nl."}, []*DNSKEY{dnskey}, test.ErrorHandler())
ctx := context.TODO()
for _, tc := range dnssecTestCases {
diff --git a/middleware/dnssec/setup.go b/middleware/dnssec/setup.go
new file mode 100644
index 000000000..999f85bf8
--- /dev/null
+++ b/middleware/dnssec/setup.go
@@ -0,0 +1,91 @@
+package dnssec
+
+import (
+ "strings"
+
+ "github.com/miekg/coredns/core/dnsserver"
+ "github.com/miekg/coredns/middleware"
+
+ "github.com/mholt/caddy"
+)
+
+func init() {
+ caddy.RegisterPlugin("dnssec", caddy.Plugin{
+ ServerType: "dns",
+ Action: setup,
+ })
+}
+
+func setup(c *caddy.Controller) error {
+ zones, keys, err := dnssecParse(c)
+ if err != nil {
+ return err
+ }
+
+ dnsserver.GetConfig(c).AddMiddleware(func(next dnsserver.Handler) dnsserver.Handler {
+ return New(zones, keys, next)
+ })
+
+ return nil
+}
+
+func dnssecParse(c *caddy.Controller) ([]string, []*DNSKEY, error) {
+ zones := []string{}
+
+ keys := []*DNSKEY{}
+ for c.Next() {
+ if c.Val() == "dnssec" {
+ // dnssec [zones...]
+ zones = make([]string, len(c.ServerBlockKeys))
+ copy(zones, c.ServerBlockKeys)
+ args := c.RemainingArgs()
+ if len(args) > 0 {
+ zones = args
+ }
+
+ for c.NextBlock() {
+ k, e := keyParse(c)
+ if e != nil {
+ return nil, nil, e
+ }
+ keys = append(keys, k...)
+ }
+ }
+ }
+ for i, _ := range zones {
+ zones[i] = middleware.Host(zones[i]).Normalize()
+ }
+ return zones, keys, nil
+}
+
+func keyParse(c *caddy.Controller) ([]*DNSKEY, error) {
+ keys := []*DNSKEY{}
+
+ what := c.Val()
+ if !c.NextArg() {
+ return nil, c.ArgErr()
+ }
+ value := c.Val()
+ switch what {
+ case "key":
+ if value == "file" {
+ ks := c.RemainingArgs()
+ for _, k := range ks {
+ base := k
+ // Kmiek.nl.+013+26205.key, handle .private or without extension: Kmiek.nl.+013+26205
+ if strings.HasSuffix(k, ".key") {
+ base = k[:len(k)-4]
+ }
+ if strings.HasSuffix(k, ".private") {
+ base = k[:len(k)-8]
+ }
+ k, err := ParseKeyFile(base+".key", base+".private")
+ if err != nil {
+ return nil, err
+ }
+ keys = append(keys, k)
+ }
+ }
+ }
+ return keys, nil
+}
diff --git a/middleware/dnssec/setup_test.go b/middleware/dnssec/setup_test.go
new file mode 100644
index 000000000..9dbeb77fd
--- /dev/null
+++ b/middleware/dnssec/setup_test.go
@@ -0,0 +1,56 @@
+package dnssec
+
+import (
+ "strings"
+ "testing"
+
+ "github.com/mholt/caddy"
+)
+
+func TestSetupDnssec(t *testing.T) {
+ tests := []struct {
+ input string
+ shouldErr bool
+ expectedZones []string
+ expectedKeys []string
+ expectedErrContent string
+ }{
+ {
+ `dnssec`, false, nil, nil, "",
+ },
+ {
+ `dnssec miek.nl`, false, []string{"miek.nl."}, nil, "",
+ },
+ }
+
+ for i, test := range tests {
+ c := caddy.NewTestController("dns", test.input)
+ zones, keys, err := dnssecParse(c)
+
+ if test.shouldErr && err == nil {
+ t.Errorf("Test %d: Expected error but found %s for input %s", i, err, test.input)
+ }
+
+ if err != nil {
+ if !test.shouldErr {
+ t.Errorf("Test %d: Expected no error but found one for input %s. Error was: %v", i, test.input, err)
+ }
+
+ if !strings.Contains(err.Error(), test.expectedErrContent) {
+ t.Errorf("Test %d: Expected error to contain: %v, found error: %v, input: %s", i, test.expectedErrContent, err, test.input)
+ }
+ }
+ if !test.shouldErr {
+ for i, z := range test.expectedZones {
+ if zones[i] != z {
+ t.Errorf("Dnssec not correctly set for input %s. Expected: %s, actual: %s", test.input, z, zones[i])
+ }
+ }
+ for i, k := range test.expectedKeys {
+ if k != keys[i].K.Header().Name {
+ t.Errorf("Dnssec not correctly set for input %s. Expected: '%s', actual: '%s'", test.input, k, keys[i].K.Header().Name)
+ }
+ }
+ }
+ }
+}
diff --git a/middleware/errors/setup.go b/middleware/errors/setup.go
new file mode 100644
index 000000000..e1c77373d
--- /dev/null
+++ b/middleware/errors/setup.go
@@ -0,0 +1,133 @@
+package errors
+
+import (
+ "io"
+ "log"
+ "os"
+
+ "github.com/miekg/coredns/core/dnsserver"
+ "github.com/miekg/coredns/middleware"
+
+ "github.com/hashicorp/go-syslog"
+ "github.com/mholt/caddy"
+)
+
+func init() {
+ caddy.RegisterPlugin("errors", caddy.Plugin{
+ ServerType: "dns",
+ Action: setup,
+ })
+}
+
+func setup(c *caddy.Controller) error {
+ handler, err := errorsParse(c)
+ if err != nil {
+ return err
+ }
+
+ var writer io.Writer
+
+ switch handler.LogFile {
+ case "visible":
+ handler.Debug = true
+ case "stdout":
+ writer = os.Stdout
+ case "stderr":
+ writer = os.Stderr
+ case "syslog":
+ writer, err = gsyslog.NewLogger(gsyslog.LOG_ERR, "LOCAL0", "coredns")
+ if err != nil {
+ return err
+ }
+ default:
+ if handler.LogFile == "" {
+ writer = os.Stderr // default
+ break
+ }
+
+ var file *os.File
+ file, err = os.OpenFile(handler.LogFile, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0644)
+ if err != nil {
+ return err
+ }
+ if handler.LogRoller != nil {
+ file.Close()
+
+ handler.LogRoller.Filename = handler.LogFile
+
+ writer = handler.LogRoller.GetLogWriter()
+ } else {
+ writer = file
+ }
+ }
+ handler.Log = log.New(writer, "", 0)
+
+ dnsserver.GetConfig(c).AddMiddleware(func(next dnsserver.Handler) dnsserver.Handler {
+ handler.Next = next
+ return handler
+ })
+
+ return nil
+}
+
+func errorsParse(c *caddy.Controller) (ErrorHandler, error) {
+ handler := ErrorHandler{}
+
+ optionalBlock := func() (bool, error) {
+ var hadBlock bool
+
+ for c.NextBlock() {
+ hadBlock = true
+
+ what := c.Val()
+ if !c.NextArg() {
+ return hadBlock, c.ArgErr()
+ }
+ where := c.Val()
+
+ if what == "log" {
+ if where == "visible" {
+ handler.Debug = true
+ } else {
+ handler.LogFile = where
+ if c.NextArg() {
+ if c.Val() == "{" {
+ c.IncrNest()
+ logRoller, err := middleware.ParseRoller(c)
+ if err != nil {
+ return hadBlock, err
+ }
+ handler.LogRoller = logRoller
+ }
+ }
+ }
+ }
+ }
+ return hadBlock, nil
+ }
+
+ for c.Next() {
+ // weird hack to avoid having the handler values overwritten.
+ if c.Val() == "}" {
+ continue
+ }
+ // Configuration may be in a block
+ hadBlock, err := optionalBlock()
+ if err != nil {
+ return handler, err
+ }
+
+ // Otherwise, the only argument would be an error log file name or 'visible'
+ if !hadBlock {
+ if c.NextArg() {
+ if c.Val() == "visible" {
+ handler.Debug = true
+ } else {
+ handler.LogFile = c.Val()
+ }
+ }
+ }
+ }
+
+ return handler, nil
+}
diff --git a/middleware/errors/setup_test.go b/middleware/errors/setup_test.go
new file mode 100644
index 000000000..6e5a85d08
--- /dev/null
+++ b/middleware/errors/setup_test.go
@@ -0,0 +1,98 @@
+package errors
+
+import (
+ "testing"
+
+ "github.com/mholt/caddy"
+ "github.com/miekg/coredns/middleware"
+)
+
+func TestErrorsParse(t *testing.T) {
+ tests := []struct {
+ inputErrorsRules string
+ shouldErr bool
+ expectedErrorHandler ErrorHandler
+ }{
+ {`errors`, false, ErrorHandler{
+ LogFile: "",
+ }},
+ {`errors errors.txt`, false, ErrorHandler{
+ LogFile: "errors.txt",
+ }},
+ {`errors visible`, false, ErrorHandler{
+ LogFile: "",
+ Debug: true,
+ }},
+ {`errors { log visible }`, false, ErrorHandler{
+ LogFile: "",
+ Debug: true,
+ }},
+ {`errors { log errors.txt { size 2 age 10 keep 3 } }`, false, ErrorHandler{
+ LogFile: "errors.txt",
+ LogRoller: &middleware.LogRoller{
+ MaxSize: 2,
+ MaxAge: 10,
+ MaxBackups: 3,
+ LocalTime: true,
+ },
+ }},
+ {`errors { log errors.txt {
+ size 3
+ age 11
+ keep 5
+ }
+}`, false, ErrorHandler{
+ LogFile: "errors.txt",
+ LogRoller: &middleware.LogRoller{
+ MaxSize: 3,
+ MaxAge: 11,
+ MaxBackups: 5,
+ LocalTime: true,
+ },
+ }},
+ }
+ for i, test := range tests {
+ c := caddy.NewTestController("dns", test.inputErrorsRules)
+ actualErrorsRule, err := errorsParse(c)
+
+ if err == nil && test.shouldErr {
+ t.Errorf("Test %d didn't error, but it should have", i)
+ } else if err != nil && !test.shouldErr {
+ t.Errorf("Test %d errored, but it shouldn't have; got '%v'", i, err)
+ }
+ if actualErrorsRule.LogFile != test.expectedErrorHandler.LogFile {
+ t.Errorf("Test %d expected LogFile to be %s, but got %s",
+ i, test.expectedErrorHandler.LogFile, actualErrorsRule.LogFile)
+ }
+ if actualErrorsRule.Debug != test.expectedErrorHandler.Debug {
+ t.Errorf("Test %d expected Debug to be %v, but got %v",
+ i, test.expectedErrorHandler.Debug, actualErrorsRule.Debug)
+ }
+ if actualErrorsRule.LogRoller != nil && test.expectedErrorHandler.LogRoller == nil || actualErrorsRule.LogRoller == nil && test.expectedErrorHandler.LogRoller != nil {
+ t.Fatalf("Test %d expected LogRoller to be %v, but got %v",
+ i, test.expectedErrorHandler.LogRoller, actualErrorsRule.LogRoller)
+ }
+ if actualErrorsRule.LogRoller != nil && test.expectedErrorHandler.LogRoller != nil {
+ if actualErrorsRule.LogRoller.Filename != test.expectedErrorHandler.LogRoller.Filename {
+ t.Fatalf("Test %d expected LogRoller Filename to be %s, but got %s",
+ i, test.expectedErrorHandler.LogRoller.Filename, actualErrorsRule.LogRoller.Filename)
+ }
+ if actualErrorsRule.LogRoller.MaxAge != test.expectedErrorHandler.LogRoller.MaxAge {
+ t.Fatalf("Test %d expected LogRoller MaxAge to be %d, but got %d",
+ i, test.expectedErrorHandler.LogRoller.MaxAge, actualErrorsRule.LogRoller.MaxAge)
+ }
+ if actualErrorsRule.LogRoller.MaxBackups != test.expectedErrorHandler.LogRoller.MaxBackups {
+ t.Fatalf("Test %d expected LogRoller MaxBackups to be %d, but got %d",
+ i, test.expectedErrorHandler.LogRoller.MaxBackups, actualErrorsRule.LogRoller.MaxBackups)
+ }
+ if actualErrorsRule.LogRoller.MaxSize != test.expectedErrorHandler.LogRoller.MaxSize {
+ t.Fatalf("Test %d expected LogRoller MaxSize to be %d, but got %d",
+ i, test.expectedErrorHandler.LogRoller.MaxSize, actualErrorsRule.LogRoller.MaxSize)
+ }
+ if actualErrorsRule.LogRoller.LocalTime != test.expectedErrorHandler.LogRoller.LocalTime {
+ t.Fatalf("Test %d expected LogRoller LocalTime to be %t, but got %t",
+ i, test.expectedErrorHandler.LogRoller.LocalTime, actualErrorsRule.LogRoller.LocalTime)
+ }
+ }
+ }
+}
diff --git a/middleware/etcd/cname_test.go b/middleware/etcd/cname_test.go
index 4a00c05c2..ee341b7b6 100644
--- a/middleware/etcd/cname_test.go
+++ b/middleware/etcd/cname_test.go
@@ -16,6 +16,8 @@ import (
// Check the ordering of returned cname.
func TestCnameLookup(t *testing.T) {
+ etc := newEtcdMiddleware()
+
for _, serv := range servicesCname {
set(t, etc, serv.Key, 0, serv)
defer delete(t, etc, serv.Key)
diff --git a/middleware/etcd/debug_test.go b/middleware/etcd/debug_test.go
index 91796816f..82de9fe1f 100644
--- a/middleware/etcd/debug_test.go
+++ b/middleware/etcd/debug_test.go
@@ -30,12 +30,13 @@ func TestIsDebug(t *testing.T) {
}
func TestDebugLookup(t *testing.T) {
+ etc := newEtcdMiddleware()
+ etc.Debug = true
+
for _, serv := range servicesDebug {
set(t, etc, serv.Key, 0, serv)
defer delete(t, etc, serv.Key)
}
- etc.Debug = true
- defer func() { etc.Debug = false }()
for _, tc := range dnsTestCasesDebug {
m := tc.Msg()
@@ -69,6 +70,8 @@ func TestDebugLookup(t *testing.T) {
}
func TestDebugLookupFalse(t *testing.T) {
+ etc := newEtcdMiddleware()
+
for _, serv := range servicesDebug {
set(t, etc, serv.Key, 0, serv)
defer delete(t, etc, serv.Key)
diff --git a/middleware/etcd/group_test.go b/middleware/etcd/group_test.go
index f5283e3ba..7a2808d45 100644
--- a/middleware/etcd/group_test.go
+++ b/middleware/etcd/group_test.go
@@ -14,6 +14,8 @@ import (
)
func TestGroupLookup(t *testing.T) {
+ etc := newEtcdMiddleware()
+
for _, serv := range servicesGroup {
set(t, etc, serv.Key, 0, serv)
defer delete(t, etc, serv.Key)
diff --git a/middleware/etcd/multi_test.go b/middleware/etcd/multi_test.go
index 19e6ca7a3..f4b59f50b 100644
--- a/middleware/etcd/multi_test.go
+++ b/middleware/etcd/multi_test.go
@@ -14,10 +14,9 @@ import (
)
func TestMultiLookup(t *testing.T) {
+ etc := newEtcdMiddleware()
etc.Zones = []string{"skydns.test.", "miek.nl."}
- defer func() { etc.Zones = []string{"skydns.test.", "skydns_extra.test.", "in-addr.arpa."} }()
etc.Next = test.ErrorHandler()
- defer func() { etc.Next = nil }()
for _, serv := range servicesMulti {
set(t, etc, serv.Key, 0, serv)
diff --git a/middleware/etcd/other_test.go b/middleware/etcd/other_test.go
index 34971c6f1..ff37d27d2 100644
--- a/middleware/etcd/other_test.go
+++ b/middleware/etcd/other_test.go
@@ -18,6 +18,8 @@ import (
)
func TestOtherLookup(t *testing.T) {
+ etc := newEtcdMiddleware()
+
for _, serv := range servicesOther {
set(t, etc, serv.Key, 0, serv)
defer delete(t, etc, serv.Key)
diff --git a/middleware/etcd/proxy_lookup_test.go b/middleware/etcd/proxy_lookup_test.go
index 9a31eee24..5e0999fb0 100644
--- a/middleware/etcd/proxy_lookup_test.go
+++ b/middleware/etcd/proxy_lookup_test.go
@@ -15,18 +15,15 @@ import (
)
func TestProxyLookupFailDebug(t *testing.T) {
+ etc := newEtcdMiddleware()
+ etc.Proxy = proxy.New([]string{"127.0.0.1:154"})
+ etc.Debug = true
+
for _, serv := range servicesProxy {
set(t, etc, serv.Key, 0, serv)
defer delete(t, etc, serv.Key)
}
- prxy := etc.Proxy
- etc.Proxy = proxy.New([]string{"127.0.0.1:154"})
- defer func() { etc.Proxy = prxy }()
-
- etc.Debug = true
- defer func() { etc.Debug = false }()
-
for _, tc := range dnsTestCasesProxy {
m := tc.Msg()
diff --git a/middleware/etcd/setup.go b/middleware/etcd/setup.go
new file mode 100644
index 000000000..dc1dddb0e
--- /dev/null
+++ b/middleware/etcd/setup.go
@@ -0,0 +1,207 @@
+package etcd
+
+import (
+ "crypto/tls"
+ "crypto/x509"
+ "io/ioutil"
+ "net"
+ "net/http"
+ "time"
+
+ "github.com/miekg/coredns/core/dnsserver"
+ "github.com/miekg/coredns/middleware"
+ "github.com/miekg/coredns/middleware/proxy"
+ "github.com/miekg/coredns/singleflight"
+
+ etcdc "github.com/coreos/etcd/client"
+ "github.com/mholt/caddy"
+ "golang.org/x/net/context"
+)
+
+func init() {
+ caddy.RegisterPlugin("etcd", caddy.Plugin{
+ ServerType: "dns",
+ Action: setup,
+ })
+}
+
+func setup(c *caddy.Controller) error {
+ e, stubzones, err := etcdParse(c)
+ if err != nil {
+ return err
+ }
+ if stubzones {
+ c.OnStartup(func() error {
+ e.UpdateStubZones()
+ return nil
+ })
+ }
+
+ dnsserver.GetConfig(c).AddMiddleware(func(next dnsserver.Handler) dnsserver.Handler {
+ e.Next = next
+ return e
+ })
+
+ return nil
+}
+
+func etcdParse(c *caddy.Controller) (*Etcd, bool, error) {
+ stub := make(map[string]proxy.Proxy)
+ etc := Etcd{
+ Proxy: proxy.New([]string{"8.8.8.8:53", "8.8.4.4:53"}),
+ PathPrefix: "skydns",
+ Ctx: context.Background(),
+ Inflight: &singleflight.Group{},
+ Stubmap: &stub,
+ }
+ var (
+ client etcdc.KeysAPI
+ tlsCertFile = ""
+ tlsKeyFile = ""
+ tlsCAcertFile = ""
+ endpoints = []string{defaultEndpoint}
+ stubzones = false
+ )
+ for c.Next() {
+ if c.Val() == "etcd" {
+ etc.Client = client
+ etc.Zones = c.RemainingArgs()
+ if len(etc.Zones) == 0 {
+ etc.Zones = make([]string, len(c.ServerBlockKeys))
+ copy(etc.Zones, c.ServerBlockKeys)
+ }
+ middleware.Zones(etc.Zones).FullyQualify()
+ if c.NextBlock() {
+ // TODO(miek): 2 switches?
+ switch c.Val() {
+ case "stubzones":
+ stubzones = true
+ case "debug":
+ etc.Debug = true
+ case "path":
+ if !c.NextArg() {
+ return &Etcd{}, false, c.ArgErr()
+ }
+ etc.PathPrefix = c.Val()
+ case "endpoint":
+ args := c.RemainingArgs()
+ if len(args) == 0 {
+ return &Etcd{}, false, c.ArgErr()
+ }
+ endpoints = args
+ case "upstream":
+ args := c.RemainingArgs()
+ if len(args) == 0 {
+ return &Etcd{}, false, c.ArgErr()
+ }
+ for i := 0; i < len(args); i++ {
+ h, p, e := net.SplitHostPort(args[i])
+ if e != nil && p == "" {
+ args[i] = h + ":53"
+ }
+ }
+ endpoints = args
+ etc.Proxy = proxy.New(args)
+ case "tls": // cert key cacertfile
+ args := c.RemainingArgs()
+ if len(args) != 3 {
+ return &Etcd{}, false, c.ArgErr()
+ }
+ tlsCertFile, tlsKeyFile, tlsCAcertFile = args[0], args[1], args[2]
+ }
+ for c.Next() {
+ switch c.Val() {
+ case "stubzones":
+ stubzones = true
+ case "debug":
+ etc.Debug = true
+ case "path":
+ if !c.NextArg() {
+ return &Etcd{}, false, c.ArgErr()
+ }
+ etc.PathPrefix = c.Val()
+ case "endpoint":
+ args := c.RemainingArgs()
+ if len(args) == 0 {
+ return &Etcd{}, false, c.ArgErr()
+ }
+ endpoints = args
+ case "upstream":
+ args := c.RemainingArgs()
+ if len(args) == 0 {
+ return &Etcd{}, false, c.ArgErr()
+ }
+ for i := 0; i < len(args); i++ {
+ h, p, e := net.SplitHostPort(args[i])
+ if e != nil && p == "" {
+ args[i] = h + ":53"
+ }
+ }
+ etc.Proxy = proxy.New(args)
+ case "tls": // cert key cacertfile
+ args := c.RemainingArgs()
+ if len(args) != 3 {
+ return &Etcd{}, false, c.ArgErr()
+ }
+ tlsCertFile, tlsKeyFile, tlsCAcertFile = args[0], args[1], args[2]
+ }
+ }
+ }
+ client, err := newEtcdClient(endpoints, tlsCertFile, tlsKeyFile, tlsCAcertFile)
+ if err != nil {
+ return &Etcd{}, false, err
+ }
+ etc.Client = client
+ return &etc, stubzones, nil
+ }
+ }
+ return &Etcd{}, false, nil
+}
+
+func newEtcdClient(endpoints []string, tlsCert, tlsKey, tlsCACert string) (etcdc.KeysAPI, error) {
+ etcdCfg := etcdc.Config{
+ Endpoints: endpoints,
+ Transport: newHTTPSTransport(tlsCert, tlsKey, tlsCACert),
+ }
+ cli, err := etcdc.New(etcdCfg)
+ if err != nil {
+ return nil, err
+ }
+ return etcdc.NewKeysAPI(cli), nil
+}
+
+func newHTTPSTransport(tlsCertFile, tlsKeyFile, tlsCACertFile string) etcdc.CancelableTransport {
+ var cc *tls.Config = nil
+
+ if tlsCertFile != "" && tlsKeyFile != "" {
+ var rpool *x509.CertPool
+ if tlsCACertFile != "" {
+ if pemBytes, err := ioutil.ReadFile(tlsCACertFile); err == nil {
+ rpool = x509.NewCertPool()
+ rpool.AppendCertsFromPEM(pemBytes)
+ }
+ }
+
+ if tlsCert, err := tls.LoadX509KeyPair(tlsCertFile, tlsKeyFile); err == nil {
+ cc = &tls.Config{
+ RootCAs: rpool,
+ Certificates: []tls.Certificate{tlsCert},
+ InsecureSkipVerify: true,
+ }
+ }
+ }
+
+ tr := &http.Transport{
+ Proxy: http.ProxyFromEnvironment,
+ Dial: (&net.Dialer{
+ Timeout: 30 * time.Second,
+ KeepAlive: 30 * time.Second,
+ }).Dial,
+ TLSHandshakeTimeout: 10 * time.Second,
+ TLSClientConfig: cc,
+ }
+
+ return tr
+}
+
+const defaultEndpoint = "http://localhost:2379"
diff --git a/middleware/etcd/setup_test.go b/middleware/etcd/setup_test.go
index 799b4a1bb..b522345d2 100644
--- a/middleware/etcd/setup_test.go
+++ b/middleware/etcd/setup_test.go
@@ -19,20 +19,19 @@ import (
"golang.org/x/net/context"
)
-var (
- etc *Etcd
- client etcdc.KeysAPI
- ctxt context.Context
-)
-
func init() {
ctxt, _ = context.WithTimeout(context.Background(), etcdTimeout)
+}
+
+// etc *Etcd
+func newEtcdMiddleware() *Etcd {
+ ctxt, _ = context.WithTimeout(context.Background(), etcdTimeout)
etcdCfg := etcdc.Config{
Endpoints: []string{"http://localhost:2379"},
}
cli, _ := etcdc.New(etcdCfg)
- etc = &Etcd{
+ return &Etcd{
Proxy: proxy.New([]string{"8.8.8.8:53"}),
PathPrefix: "skydns",
Ctx: context.Background(),
@@ -57,10 +56,12 @@ func delete(t *testing.T, e *Etcd, k string) {
}
func TestLookup(t *testing.T) {
+ etc := newEtcdMiddleware()
for _, serv := range services {
set(t, etc, serv.Key, 0, serv)
defer delete(t, etc, serv.Key)
}
+
for _, tc := range dnsTestCases {
m := tc.Msg()
@@ -91,3 +92,5 @@ func TestLookup(t *testing.T) {
}
}
}
+
+var ctxt context.Context
diff --git a/middleware/etcd/stub_test.go b/middleware/etcd/stub_test.go
index 1dc0901c0..b5a101dad 100644
--- a/middleware/etcd/stub_test.go
+++ b/middleware/etcd/stub_test.go
@@ -41,13 +41,14 @@ func TestStubLookup(t *testing.T) {
exampleNetStub := &msg.Service{Host: host, Port: port, Key: "a.example.net.stub.dns.skydns.test."}
servicesStub = append(servicesStub, exampleNetStub)
+ etc := newEtcdMiddleware()
+
for _, serv := range servicesStub {
set(t, etc, serv.Key, 0, serv)
defer delete(t, etc, serv.Key)
}
etc.updateStubZones()
- defer func() { etc.Stubmap = nil }()
for _, tc := range dnsTestCasesStub {
m := tc.Msg()
diff --git a/middleware/file/setup.go b/middleware/file/setup.go
new file mode 100644
index 000000000..8b44650ee
--- /dev/null
+++ b/middleware/file/setup.go
@@ -0,0 +1,143 @@
+package file
+
+import (
+ "fmt"
+ "net"
+ "os"
+
+ "github.com/miekg/coredns/core/dnsserver"
+ "github.com/miekg/coredns/middleware"
+
+ "github.com/mholt/caddy"
+)
+
+func init() {
+ caddy.RegisterPlugin("file", caddy.Plugin{
+ ServerType: "dns",
+ Action: setup,
+ })
+}
+
+func setup(c *caddy.Controller) error {
+ zones, err := fileParse(c)
+ if err != nil {
+ return err
+ }
+
+ // Add startup functions to notify the master(s).
+ for _, n := range zones.Names {
+ c.OnStartup(func() error {
+ zones.Z[n].StartupOnce.Do(func() {
+ if len(zones.Z[n].TransferTo) > 0 {
+ zones.Z[n].Notify()
+ }
+ zones.Z[n].Reload(nil)
+ })
+ return nil
+ })
+ }
+
+ dnsserver.GetConfig(c).AddMiddleware(func(next dnsserver.Handler) dnsserver.Handler {
+ return File{Next: next, Zones: zones}
+ })
+
+ return nil
+}
+
+func fileParse(c *caddy.Controller) (Zones, error) {
+ z := make(map[string]*Zone)
+ names := []string{}
+ origins := []string{}
+
+ for c.Next() {
+ if c.Val() == "file" {
+ // file db.file [zones...]
+ if !c.NextArg() {
+ return Zones{}, c.ArgErr()
+ }
+ fileName := c.Val()
+
+ origins = make([]string, len(c.ServerBlockKeys))
+ copy(origins, c.ServerBlockKeys)
+ args := c.RemainingArgs()
+ if len(args) > 0 {
+ origins = args
+ }
+
+ reader, err := os.Open(fileName)
+ if err != nil {
+ // bail out
+ return Zones{}, err
+ }
+
+ for i, _ := range origins {
+ origins[i] = middleware.Host(origins[i]).Normalize()
+ zone, err := Parse(reader, origins[i], fileName)
+ if err == nil {
+ z[origins[i]] = zone
+ } else {
+ return Zones{}, err
+ }
+ names = append(names, origins[i])
+ }
+
+ noReload := false
+ for c.NextBlock() {
+ t, _, e := TransferParse(c)
+ if e != nil {
+ return Zones{}, e
+ }
+ switch c.Val() {
+ case "no_reload":
+ noReload = true
+ }
+ // discard from, here, maybe check and show log when we do?
+ for _, origin := range origins {
+ if t != nil {
+ z[origin].TransferTo = append(z[origin].TransferTo, t...)
+ }
+ z[origin].NoReload = noReload
+ }
+ }
+ }
+ }
+ return Zones{Z: z, Names: names}, nil
+}
+
+// TransferParse parses transfer statements: 'transfer to [address...]'.
+// Exported so secondary can use this as well.
+func TransferParse(c *caddy.Controller) (tos, froms []string, err error) {
+ what := c.Val()
+ if !c.NextArg() {
+ return nil, nil, c.ArgErr()
+ }
+ value := c.Val()
+ switch what {
+ case "transfer":
+ if value == "to" {
+ tos = c.RemainingArgs()
+ for i, _ := range tos {
+ if tos[i] != "*" {
+ if x := net.ParseIP(tos[i]); x == nil {
+ return nil, nil, fmt.Errorf("must specify an IP addres: `%s'", tos[i])
+ }
+ tos[i] = middleware.Addr(tos[i]).Normalize()
+ }
+ }
+ }
+ if value == "from" {
+ froms = c.RemainingArgs()
+ for i, _ := range froms {
+ if froms[i] != "*" {
+ if x := net.ParseIP(froms[i]); x == nil {
+ return nil, nil, fmt.Errorf("must specify an IP addres: `%s'", froms[i])
+ }
+ froms[i] = middleware.Addr(froms[i]).Normalize()
+ } else {
+ return nil, nil, fmt.Errorf("can't use '*' in transfer from")
+ }
+ }
+ }
+ }
+ return
+}
diff --git a/middleware/health/health.go b/middleware/health/health.go
index 035c9ca7a..1d47e409e 100644
--- a/middleware/health/health.go
+++ b/middleware/health/health.go
@@ -12,15 +12,16 @@ var once sync.Once
type Health struct {
Addr string
- ln net.Listener
- mux *http.ServeMux
+
+ ln net.Listener
+ mux *http.ServeMux
}
func health(w http.ResponseWriter, r *http.Request) {
io.WriteString(w, ok)
}
-func (h *Health) Start() error {
+func (h *Health) Startup() error {
if h.Addr == "" {
h.Addr = defAddr
}
diff --git a/middleware/health/setup.go b/middleware/health/setup.go
new file mode 100644
index 000000000..cf7667d17
--- /dev/null
+++ b/middleware/health/setup.go
@@ -0,0 +1,42 @@
+package health
+
+import "github.com/mholt/caddy"
+
+func init() {
+ caddy.RegisterPlugin("health", caddy.Plugin{
+ ServerType: "dns",
+ Action: setup,
+ })
+}
+
+func setup(c *caddy.Controller) error {
+ addr, err := healthParse(c)
+ if err != nil {
+ return err
+ }
+
+ health := &Health{Addr: addr}
+ c.OnStartup(health.Startup)
+ c.OnShutdown(health.Shutdown)
+
+ // Don't do AddMiddleware, as health is not *really* a middleware just a separate
+ // webserver running.
+
+ return nil
+}
+
+func healthParse(c *caddy.Controller) (string, error) {
+ addr := ""
+ for c.Next() {
+ args := c.RemainingArgs()
+
+ switch len(args) {
+ case 0:
+ case 1:
+ addr = args[0]
+ default:
+ return "", c.ArgErr()
+ }
+ }
+ return addr, nil
+}
diff --git a/middleware/kubernetes/controller.go b/middleware/kubernetes/controller.go
index 3fbea313e..5de16d61c 100644
--- a/middleware/kubernetes/controller.go
+++ b/middleware/kubernetes/controller.go
@@ -2,7 +2,6 @@ package kubernetes
import (
"fmt"
- "log"
"sync"
"time"
@@ -12,7 +11,7 @@ import (
"k8s.io/kubernetes/pkg/client/cache"
client "k8s.io/kubernetes/pkg/client/unversioned"
"k8s.io/kubernetes/pkg/controller/framework"
- "k8s.io/kubernetes/pkg/labels"
+ "k8s.io/kubernetes/pkg/labels"
"k8s.io/kubernetes/pkg/runtime"
"k8s.io/kubernetes/pkg/watch"
)
@@ -24,7 +23,7 @@ var (
type dnsController struct {
client *client.Client
- selector *labels.Selector
+ selector *labels.Selector
endpController *framework.Controller
svcController *framework.Controller
@@ -45,9 +44,9 @@ type dnsController struct {
// newDNSController creates a controller for coredns
func newdnsController(kubeClient *client.Client, resyncPeriod time.Duration, lselector *labels.Selector) *dnsController {
dns := dnsController{
- client: kubeClient,
- selector: lselector,
- stopCh: make(chan struct{}),
+ client: kubeClient,
+ selector: lselector,
+ stopCh: make(chan struct{}),
}
dns.endpLister.Store, dns.endpController = framework.NewInformer(
@@ -76,54 +75,54 @@ func newdnsController(kubeClient *client.Client, resyncPeriod time.Duration, lse
func serviceListFunc(c *client.Client, ns string, s *labels.Selector) func(api.ListOptions) (runtime.Object, error) {
return func(opts api.ListOptions) (runtime.Object, error) {
- if s != nil {
- opts.LabelSelector = *s
- }
+ if s != nil {
+ opts.LabelSelector = *s
+ }
return c.Services(ns).List(opts)
}
}
func serviceWatchFunc(c *client.Client, ns string, s *labels.Selector) func(options api.ListOptions) (watch.Interface, error) {
return func(options api.ListOptions) (watch.Interface, error) {
- if s != nil {
- options.LabelSelector = *s
- }
+ if s != nil {
+ options.LabelSelector = *s
+ }
return c.Services(ns).Watch(options)
}
}
func endpointsListFunc(c *client.Client, ns string, s *labels.Selector) func(api.ListOptions) (runtime.Object, error) {
return func(opts api.ListOptions) (runtime.Object, error) {
- if s != nil {
- opts.LabelSelector = *s
- }
+ if s != nil {
+ opts.LabelSelector = *s
+ }
return c.Endpoints(ns).List(opts)
}
}
func endpointsWatchFunc(c *client.Client, ns string, s *labels.Selector) func(options api.ListOptions) (watch.Interface, error) {
return func(options api.ListOptions) (watch.Interface, error) {
- if s != nil {
- options.LabelSelector = *s
- }
+ if s != nil {
+ options.LabelSelector = *s
+ }
return c.Endpoints(ns).Watch(options)
}
}
func namespaceListFunc(c *client.Client, s *labels.Selector) func(api.ListOptions) (runtime.Object, error) {
return func(opts api.ListOptions) (runtime.Object, error) {
- if s != nil {
- opts.LabelSelector = *s
- }
+ if s != nil {
+ opts.LabelSelector = *s
+ }
return c.Namespaces().List(opts)
}
}
func namespaceWatchFunc(c *client.Client, s *labels.Selector) func(options api.ListOptions) (watch.Interface, error) {
return func(options api.ListOptions) (watch.Interface, error) {
- if s != nil {
- options.LabelSelector = *s
- }
+ if s != nil {
+ options.LabelSelector = *s
+ }
return c.Namespaces().Watch(options)
}
}
@@ -140,7 +139,6 @@ func (dns *dnsController) Stop() error {
// Only try draining the workqueue if we haven't already.
if !dns.shutdown {
close(dns.stopCh)
- log.Println("shutting down controller queues")
dns.shutdown = true
return nil
@@ -151,14 +149,10 @@ func (dns *dnsController) Stop() error {
// Run starts the controller.
func (dns *dnsController) Run() {
- log.Println("[debug] Starting k8s notification controllers")
-
go dns.endpController.Run(dns.stopCh)
go dns.svcController.Run(dns.stopCh)
go dns.nsController.Run(dns.stopCh)
-
<-dns.stopCh
- log.Println("[debug] shutting down coredns controller")
}
func (dns *dnsController) GetNamespaceList() *api.NamespaceList {
@@ -203,12 +197,12 @@ func (dns *dnsController) GetServiceInNamespace(namespace string, servicename st
svcObj, svcExists, err := dns.svcLister.Store.GetByKey(svcKey)
if err != nil {
- log.Printf("error getting service %v from the cache: %v\n", svcKey, err)
+ // TODO(...): should return err here
return nil
}
if !svcExists {
- log.Printf("service %v does not exists\n", svcKey)
+ // TODO(...): should return err here
return nil
}
diff --git a/middleware/kubernetes/handler.go b/middleware/kubernetes/handler.go
index 05dfba934..1986820d5 100644
--- a/middleware/kubernetes/handler.go
+++ b/middleware/kubernetes/handler.go
@@ -2,7 +2,6 @@ package kubernetes
import (
"fmt"
- "log"
"strings"
"github.com/miekg/coredns/middleware"
@@ -12,8 +11,6 @@ import (
)
func (k Kubernetes) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) {
- log.Printf("[debug] here entering ServeDNS: ctx:%v dnsmsg:%v\n", ctx, r)
-
state := middleware.State{W: w, Req: r}
if state.QClass() != dns.ClassINET {
return dns.RcodeServerFailure, fmt.Errorf("can only deal with ClassINET")
diff --git a/middleware/kubernetes/kubernetes.go b/middleware/kubernetes/kubernetes.go
index 5e2a1bf53..59a044140 100644
--- a/middleware/kubernetes/kubernetes.go
+++ b/middleware/kubernetes/kubernetes.go
@@ -16,10 +16,10 @@ import (
"github.com/miekg/dns"
"k8s.io/kubernetes/pkg/api"
unversionedapi "k8s.io/kubernetes/pkg/api/unversioned"
- "k8s.io/kubernetes/pkg/labels"
unversionedclient "k8s.io/kubernetes/pkg/client/unversioned"
"k8s.io/kubernetes/pkg/client/unversioned/clientcmd"
clientcmdapi "k8s.io/kubernetes/pkg/client/unversioned/clientcmd/api"
+ "k8s.io/kubernetes/pkg/labels"
)
type Kubernetes struct {
@@ -32,10 +32,10 @@ type Kubernetes struct {
NameTemplate *nametemplate.NameTemplate
Namespaces []string
LabelSelector *unversionedapi.LabelSelector
- Selector *labels.Selector
+ Selector *labels.Selector
}
-func (g *Kubernetes) StartKubeCache() error {
+func (g *Kubernetes) InitKubeCache() error {
// For a custom api server or running outside a k8s cluster
// set URL in env.KUBERNETES_MASTER or set endpoint in Corefile
loadingRules := clientcmd.NewDefaultClientConfigLoadingRules()
@@ -46,7 +46,6 @@ func (g *Kubernetes) StartKubeCache() error {
clientConfig := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(loadingRules, overrides)
config, err := clientConfig.ClientConfig()
if err != nil {
- log.Printf("[debug] error connecting to the client: %v", err)
return err
}
kubeClient, err := unversionedclient.New(config)
@@ -58,20 +57,17 @@ func (g *Kubernetes) StartKubeCache() error {
if g.LabelSelector == nil {
log.Printf("[INFO] Kubernetes middleware configured without a label selector. No label-based filtering will be performed.")
} else {
- var selector labels.Selector
+ var selector labels.Selector
selector, err = unversionedapi.LabelSelectorAsSelector(g.LabelSelector)
- g.Selector = &selector
- if err != nil {
- log.Printf("[ERROR] Unable to create Selector for LabelSelector '%s'.Error was: %s", g.LabelSelector, err)
- return err
- }
+ g.Selector = &selector
+ if err != nil {
+ log.Printf("[ERROR] Unable to create Selector for LabelSelector '%s'.Error was: %s", g.LabelSelector, err)
+ return err
+ }
log.Printf("[INFO] Kubernetes middleware configured with the label selector '%s'. Only kubernetes objects matching this label selector will be exposed.", unversionedapi.FormatLabelSelector(g.LabelSelector))
}
- log.Printf("[debug] Starting kubernetes middleware with k8s API resync period: %s", g.ResyncPeriod)
g.APIConn = newdnsController(kubeClient, g.ResyncPeriod, g.Selector)
- go g.APIConn.Run()
-
return err
}
@@ -115,7 +111,6 @@ func (g *Kubernetes) Records(name string, exact bool) ([]msg.Service, error) {
typeName string
)
- log.Printf("[debug] enter Records('%v', '%v')\n", name, exact)
zone, serviceSegments := g.getZoneForName(name)
// TODO: Implementation above globbed together segments for the serviceName if
@@ -137,30 +132,18 @@ func (g *Kubernetes) Records(name string, exact bool) ([]msg.Service, error) {
serviceName = util.WildcardStar
}
- log.Printf("[debug] published namespaces: %v\n", g.Namespaces)
-
- log.Printf("[debug] exact: %v\n", exact)
- log.Printf("[debug] zone: %v\n", zone)
- log.Printf("[debug] servicename: %v\n", serviceName)
- log.Printf("[debug] namespace: %v\n", namespace)
- log.Printf("[debug] typeName: %v\n", typeName)
- log.Printf("[debug] APIconn: %v\n", g.APIConn)
-
nsWildcard := util.SymbolContainsWildcard(namespace)
serviceWildcard := util.SymbolContainsWildcard(serviceName)
// Abort if the namespace does not contain a wildcard, and namespace is not published per CoreFile
// Case where namespace contains a wildcard is handled in Get(...) method.
if (!nsWildcard) && (len(g.Namespaces) > 0) && (!util.StringInSlice(namespace, g.Namespaces)) {
- log.Printf("[debug] Namespace '%v' is not published by Corefile\n", namespace)
return nil, nil
}
- log.Printf("before g.Get(namespace, nsWildcard, serviceName, serviceWildcard): %v %v %v %v", namespace, nsWildcard, serviceName, serviceWildcard)
+ log.Printf("[debug] before g.Get(namespace, nsWildcard, serviceName, serviceWildcard): %v %v %v %v", namespace, nsWildcard, serviceName, serviceWildcard)
k8sItems, err := g.Get(namespace, nsWildcard, serviceName, serviceWildcard)
- log.Printf("[debug] k8s items: %v\n", k8sItems)
if err != nil {
- log.Printf("[ERROR] Got error while looking up ServiceItems. Error is: %v\n", err)
return nil, err
}
if k8sItems == nil {
@@ -178,7 +161,6 @@ func (g *Kubernetes) getRecordsForServiceItems(serviceItems []api.Service, value
for _, item := range serviceItems {
clusterIP := item.Spec.ClusterIP
- log.Printf("[debug] clusterIP: %v\n", clusterIP)
// Create records by constructing record name from template...
//values.Namespace = item.Metadata.Namespace
@@ -188,13 +170,11 @@ func (g *Kubernetes) getRecordsForServiceItems(serviceItems []api.Service, value
// Create records for each exposed port...
for _, p := range item.Spec.Ports {
- log.Printf("[debug] port: %v\n", p.Port)
s := msg.Service{Host: clusterIP, Port: int(p.Port)}
records = append(records, s)
}
}
- log.Printf("[debug] records from getRecordsForServiceItems(): %v\n", records)
return records
}
@@ -202,13 +182,6 @@ func (g *Kubernetes) getRecordsForServiceItems(serviceItems []api.Service, value
func (g *Kubernetes) Get(namespace string, nsWildcard bool, servicename string, serviceWildcard bool) ([]api.Service, error) {
serviceList := g.APIConn.GetServiceList()
- /* TODO: Remove?
- if err != nil {
- log.Printf("[ERROR] Getting service list produced error: %v", err)
- return nil, err
- }
- */
-
var resultItems []api.Service
for _, item := range serviceList.Items {
@@ -216,7 +189,6 @@ func (g *Kubernetes) Get(namespace string, nsWildcard bool, servicename string,
// If namespace has a wildcard, filter results against Corefile namespace list.
// (Namespaces without a wildcard were filtered before the call to this function.)
if nsWildcard && (len(g.Namespaces) > 0) && (!util.StringInSlice(item.Namespace, g.Namespaces)) {
- log.Printf("[debug] Namespace '%v' is not published by Corefile\n", item.Namespace)
continue
}
resultItems = append(resultItems, item)
diff --git a/middleware/kubernetes/nametemplate/nametemplate.go b/middleware/kubernetes/nametemplate/nametemplate.go
index 5a34ae4ad..3e1ac4bb3 100644
--- a/middleware/kubernetes/nametemplate/nametemplate.go
+++ b/middleware/kubernetes/nametemplate/nametemplate.go
@@ -2,7 +2,6 @@ package nametemplate
import (
"errors"
- "log"
"strings"
"github.com/miekg/coredns/middleware/kubernetes/util"
@@ -87,17 +86,13 @@ func (t *NameTemplate) SetTemplate(s string) error {
if !elementPositionSet {
if strings.Contains(v, "{") {
err = errors.New("Record name template contains the unknown symbol '" + v + "'")
- log.Printf("[debug] %v\n", err)
return err
- } else {
- log.Printf("[debug] Template string has static element '%v'\n", v)
}
}
}
if err == nil && !t.IsValid() {
err = errors.New("Record name template does not pass NameTemplate validation")
- log.Printf("[debug] %v\n", err)
return err
}
diff --git a/middleware/kubernetes/setup.go b/middleware/kubernetes/setup.go
new file mode 100644
index 000000000..fc3c036b8
--- /dev/null
+++ b/middleware/kubernetes/setup.go
@@ -0,0 +1,140 @@
+package kubernetes
+
+import (
+ "errors"
+ "fmt"
+ "strings"
+ "time"
+
+ "github.com/miekg/coredns/core/dnsserver"
+ "github.com/miekg/coredns/middleware"
+ "github.com/miekg/coredns/middleware/kubernetes/nametemplate"
+
+ "github.com/mholt/caddy"
+ unversionedapi "k8s.io/kubernetes/pkg/api/unversioned"
+)
+
+func init() {
+ caddy.RegisterPlugin("kubernetes", caddy.Plugin{
+ ServerType: "dns",
+ Action: setup,
+ })
+}
+
+func setup(c *caddy.Controller) error {
+ kubernetes, err := kubernetesParse(c)
+ if err != nil {
+ return err
+ }
+
+ err = kubernetes.InitKubeCache()
+ if err != nil {
+ return err
+ }
+
+ // Register KubeCache start and stop functions with Caddy
+ c.OnStartup(func() error {
+ go kubernetes.APIConn.Run()
+ return nil
+ })
+
+ c.OnShutdown(func() error {
+ return kubernetes.APIConn.Stop()
+ })
+
+ dnsserver.GetConfig(c).AddMiddleware(func(next dnsserver.Handler) dnsserver.Handler {
+ kubernetes.Next = next
+ return kubernetes
+ })
+
+ return nil
+}
+
+func kubernetesParse(c *caddy.Controller) (Kubernetes, error) {
+ var err error
+ template := defaultNameTemplate
+
+ k8s := Kubernetes{ResyncPeriod: defaultResyncPeriod}
+ k8s.NameTemplate = new(nametemplate.NameTemplate)
+ k8s.NameTemplate.SetTemplate(template)
+
+ for c.Next() {
+ if c.Val() == "kubernetes" {
+ zones := c.RemainingArgs()
+
+ if len(zones) == 0 {
+ k8s.Zones = make([]string, len(c.ServerBlockKeys))
+ copy(k8s.Zones, c.ServerBlockKeys)
+ }
+
+ k8s.Zones = NormalizeZoneList(zones)
+ middleware.Zones(k8s.Zones).FullyQualify()
+
+ if k8s.Zones == nil || len(k8s.Zones) < 1 {
+ err = errors.New("Zone name must be provided for kubernetes middleware.")
+ return Kubernetes{}, err
+ }
+
+ for c.NextBlock() {
+ switch c.Val() {
+ case "template":
+ args := c.RemainingArgs()
+ if len(args) != 0 {
+ template := strings.Join(args, "")
+ err = k8s.NameTemplate.SetTemplate(template)
+ if err != nil {
+ return Kubernetes{}, err
+ }
+ } else {
+ return Kubernetes{}, c.ArgErr()
+ }
+ case "namespaces":
+ args := c.RemainingArgs()
+ if len(args) != 0 {
+ k8s.Namespaces = append(k8s.Namespaces, args...)
+ } else {
+ return Kubernetes{}, c.ArgErr()
+ }
+ case "endpoint":
+ args := c.RemainingArgs()
+ if len(args) != 0 {
+ k8s.APIEndpoint = args[0]
+ } else {
+ return Kubernetes{}, c.ArgErr()
+ }
+ case "resyncperiod":
+ args := c.RemainingArgs()
+ if len(args) != 0 {
+ k8s.ResyncPeriod, err = time.ParseDuration(args[0])
+ if err != nil {
+ err = errors.New(fmt.Sprintf("Unable to parse resync duration value. Value provided was '%v'. Example valid values: '15s', '5m', '1h'. Error was: %v", args[0], err))
+ return Kubernetes{}, err
+ }
+ } else {
+ return Kubernetes{}, c.ArgErr()
+ }
+ case "labels":
+ args := c.RemainingArgs()
+ if len(args) != 0 {
+ labelSelectorString := strings.Join(args, " ")
+ k8s.LabelSelector, err = unversionedapi.ParseToLabelSelector(labelSelectorString)
+ if err != nil {
+ err = errors.New(fmt.Sprintf("Unable to parse label selector. Value provided was '%v'. Error was: %v", labelSelectorString, err))
+ return Kubernetes{}, err
+ }
+ } else {
+ return Kubernetes{}, c.ArgErr()
+ }
+ }
+ }
+ return k8s, nil
+ }
+ }
+ err = errors.New("Kubernetes setup called without keyword 'kubernetes' in Corefile")
+ return Kubernetes{}, err
+}
+
+const (
+ defaultNameTemplate = "{service}.{namespace}.{zone}"
+ defaultResyncPeriod = 5 * time.Minute
+)
diff --git a/middleware/kubernetes/setup_test.go b/middleware/kubernetes/setup_test.go
new file mode 100644
index 000000000..6e0918a3b
--- /dev/null
+++ b/middleware/kubernetes/setup_test.go
@@ -0,0 +1,390 @@
+package kubernetes
+
+import (
+ "strings"
+ "testing"
+ "time"
+
+ "github.com/mholt/caddy"
+ unversionedapi "k8s.io/kubernetes/pkg/api/unversioned"
+)
+
+func TestKubernetesParse(t *testing.T) {
+ tests := []struct {
+ description string // Human-facing description of test case
+ input string // Corefile data as string
+ shouldErr bool // true if test case is exected to produce an error.
+ expectedErrContent string // substring from the expected error. Empty for positive cases.
+ expectedZoneCount int // expected count of defined zones.
+ expectedNTValid bool // NameTemplate to be initialized and valid
+ expectedNSCount int // expected count of namespaces.
+ expectedResyncPeriod time.Duration // expected resync period value
+ expectedLabelSelector string // expected label selector value
+ }{
+ // positive
+ {
+ "kubernetes keyword with one zone",
+ `kubernetes coredns.local`,
+ false,
+ "",
+ 1,
+ true,
+ 0,
+ defaultResyncPeriod,
+ "",
+ },
+ {
+ "kubernetes keyword with multiple zones",
+ `kubernetes coredns.local test.local`,
+ false,
+ "",
+ 2,
+ true,
+ 0,
+ defaultResyncPeriod,
+ "",
+ },
+ {
+ "kubernetes keyword with zone and empty braces",
+ `kubernetes coredns.local {
+}`,
+ false,
+ "",
+ 1,
+ true,
+ 0,
+ defaultResyncPeriod,
+ "",
+ },
+ {
+ "endpoint keyword with url",
+ `kubernetes coredns.local {
+ endpoint http://localhost:9090
+}`,
+ false,
+ "",
+ 1,
+ true,
+ 0,
+ defaultResyncPeriod,
+ "",
+ },
+ {
+ "template keyword with valid template",
+ `kubernetes coredns.local {
+ template {service}.{namespace}.{zone}
+}`,
+ false,
+ "",
+ 1,
+ true,
+ 0,
+ defaultResyncPeriod,
+ "",
+ },
+ {
+ "namespaces keyword with one namespace",
+ `kubernetes coredns.local {
+ namespaces demo
+}`,
+ false,
+ "",
+ 1,
+ true,
+ 1,
+ defaultResyncPeriod,
+ "",
+ },
+ {
+ "namespaces keyword with multiple namespaces",
+ `kubernetes coredns.local {
+ namespaces demo test
+}`,
+ false,
+ "",
+ 1,
+ true,
+ 2,
+ defaultResyncPeriod,
+ "",
+ },
+ {
+ "resync period in seconds",
+ `kubernetes coredns.local {
+ resyncperiod 30s
+}`,
+ false,
+ "",
+ 1,
+ true,
+ 0,
+ 30 * time.Second,
+ "",
+ },
+ {
+ "resync period in minutes",
+ `kubernetes coredns.local {
+ resyncperiod 15m
+}`,
+ false,
+ "",
+ 1,
+ true,
+ 0,
+ 15 * time.Minute,
+ "",
+ },
+ {
+ "basic label selector",
+ `kubernetes coredns.local {
+ labels environment=prod
+}`,
+ false,
+ "",
+ 1,
+ true,
+ 0,
+ defaultResyncPeriod,
+ "environment=prod",
+ },
+ {
+ "multi-label selector",
+ `kubernetes coredns.local {
+ labels environment in (production, staging, qa),application=nginx
+}`,
+ false,
+ "",
+ 1,
+ true,
+ 0,
+ defaultResyncPeriod,
+ "application=nginx,environment in (production,qa,staging)",
+ },
+ {
+ "fully specified valid config",
+ `kubernetes coredns.local test.local {
+ resyncperiod 15m
+ endpoint http://localhost:8080
+ template {service}.{namespace}.{zone}
+ namespaces demo test
+ labels environment in (production, staging, qa),application=nginx
+}`,
+ false,
+ "",
+ 2,
+ true,
+ 2,
+ 15 * time.Minute,
+ "application=nginx,environment in (production,qa,staging)",
+ },
+ // negative
+ {
+ "no kubernetes keyword",
+ "",
+ true,
+ "Kubernetes setup called without keyword 'kubernetes' in Corefile",
+ -1,
+ false,
+ -1,
+ defaultResyncPeriod,
+ "",
+ },
+ {
+ "kubernetes keyword without a zone",
+ `kubernetes`,
+ true,
+ "Zone name must be provided for kubernetes middleware",
+ -1,
+ true,
+ 0,
+ defaultResyncPeriod,
+ "",
+ },
+ {
+ "endpoint keyword without an endpoint value",
+ `kubernetes coredns.local {
+ endpoint
+}`,
+ true,
+ "Wrong argument count or unexpected line ending after 'endpoint'",
+ -1,
+ true,
+ -1,
+ defaultResyncPeriod,
+ "",
+ },
+ {
+ "template keyword without a template value",
+ `kubernetes coredns.local {
+ template
+}`,
+ true,
+ "Wrong argument count or unexpected line ending after 'template'",
+ -1,
+ false,
+ 0,
+ defaultResyncPeriod,
+ "",
+ },
+ {
+ "template keyword with an invalid template value",
+ `kubernetes coredns.local {
+ template {namespace}.{zone}
+}`,
+ true,
+ "Record name template does not pass NameTemplate validation",
+ -1,
+ false,
+ 0,
+ defaultResyncPeriod,
+ "",
+ },
+ {
+ "namespace keyword without a namespace value",
+ `kubernetes coredns.local {
+ namespaces
+}`,
+ true,
+ "Parse error: Wrong argument count or unexpected line ending after 'namespaces'",
+ -1,
+ true,
+ -1,
+ defaultResyncPeriod,
+ "",
+ },
+ {
+ "resyncperiod keyword without a duration value",
+ `kubernetes coredns.local {
+ resyncperiod
+}`,
+ true,
+ "Wrong argument count or unexpected line ending after 'resyncperiod'",
+ -1,
+ true,
+ 0,
+ 0 * time.Minute,
+ "",
+ },
+ {
+ "resync period no units",
+ `kubernetes coredns.local {
+ resyncperiod 15
+}`,
+ true,
+ "Unable to parse resync duration value. Value provided was ",
+ -1,
+ true,
+ 0,
+ 0 * time.Second,
+ "",
+ },
+ {
+ "resync period invalid",
+ `kubernetes coredns.local {
+ resyncperiod abc
+}`,
+ true,
+ "Unable to parse resync duration value. Value provided was ",
+ -1,
+ true,
+ 0,
+ 0 * time.Second,
+ "",
+ },
+ {
+ "labels with no selector value",
+ `kubernetes coredns.local {
+ labels
+}`,
+ true,
+ "Wrong argument count or unexpected line ending after 'labels'",
+ -1,
+ true,
+ 0,
+ 0 * time.Second,
+ "",
+ },
+ {
+ "labels with invalid selector value",
+ `kubernetes coredns.local {
+ labels environment in (production, qa
+}`,
+ true,
+ "Unable to parse label selector. Value provided was",
+ -1,
+ true,
+ 0,
+ 0 * time.Second,
+ "",
+ },
+ }
+
+ t.Logf("Parser test cases count: %v", len(tests))
+ for i, test := range tests {
+ c := caddy.NewTestController("dns", test.input)
+ k8sController, err := kubernetesParse(c)
+ t.Logf("setup test: %2v -- %v\n", i, test.description)
+ //t.Logf("controller: %v\n", k8sController)
+
+ if test.shouldErr && err == nil {
+ t.Errorf("Test %d: Expected error, but did not find error for input '%s'. Error was: '%v'", i, test.input, err)
+ }
+
+ if err != nil {
+ if !test.shouldErr {
+ t.Errorf("Test %d: Expected no error but found one for input %s. Error was: %v", i, test.input, err)
+ continue
+ }
+
+ if test.shouldErr && (len(test.expectedErrContent) < 1) {
+ t.Fatalf("Test %d: Test marked as expecting an error, but no expectedErrContent provided for input '%s'. Error was: '%v'", i, test.input, err)
+ }
+
+ if test.shouldErr && (test.expectedZoneCount >= 0) {
+ t.Errorf("Test %d: Test marked as expecting an error, but provides value for expectedZoneCount!=-1 for input '%s'. Error was: '%v'", i, test.input, err)
+ }
+
+ if !strings.Contains(err.Error(), test.expectedErrContent) {
+ t.Errorf("Test %d: Expected error to contain: %v, found error: %v, input: %s", i, test.expectedErrContent, err, test.input)
+ }
+ continue
+ }
+
+ // No error was raised, so validate initialization of k8sController
+ // Zones
+ foundZoneCount := len(k8sController.Zones)
+ if foundZoneCount != test.expectedZoneCount {
+ t.Errorf("Test %d: Expected kubernetes controller to be initialized with %d zones, instead found %d zones: '%v' for input '%s'", i, test.expectedZoneCount, foundZoneCount, k8sController.Zones, test.input)
+ }
+
+ // NameTemplate
+ if k8sController.NameTemplate == nil {
+ t.Errorf("Test %d: Expected kubernetes controller to be initialized with a NameTemplate. Instead found '%v' for input '%s'", i, k8sController.NameTemplate, test.input)
+ } else {
+ foundNTValid := k8sController.NameTemplate.IsValid()
+ if foundNTValid != test.expectedNTValid {
+ t.Errorf("Test %d: Expected NameTemplate validity to be '%v', instead found '%v' for input '%s'", i, test.expectedNTValid, foundNTValid, test.input)
+ }
+ }
+
+ // Namespaces
+ foundNSCount := len(k8sController.Namespaces)
+ if foundNSCount != test.expectedNSCount {
+ t.Errorf("Test %d: Expected kubernetes controller to be initialized with %d namespaces. Instead found %d namespaces: '%v' for input '%s'", i, test.expectedNSCount, foundNSCount, k8sController.Namespaces, test.input)
+ }
+
+ // ResyncPeriod
+ foundResyncPeriod := k8sController.ResyncPeriod
+ if foundResyncPeriod != test.expectedResyncPeriod {
+ t.Errorf("Test %d: Expected kubernetes controller to be initialized with resync period '%s'. Instead found period '%s' for input '%s'", i, test.expectedResyncPeriod, foundResyncPeriod, test.input)
+ }
+
+ // Labels
+ if k8sController.LabelSelector != nil {
+ foundLabelSelectorString := unversionedapi.FormatLabelSelector(k8sController.LabelSelector)
+ if foundLabelSelectorString != test.expectedLabelSelector {
+ t.Errorf("Test %d: Expected kubernetes controller to be initialized with label selector '%s'. Instead found selector '%s' for input '%s'", i, test.expectedLabelSelector, foundLabelSelectorString, test.input)
+ }
+ }
+ }
+}
diff --git a/middleware/loadbalance/setup.go b/middleware/loadbalance/setup.go
new file mode 100644
index 000000000..ef3f35f03
--- /dev/null
+++ b/middleware/loadbalance/setup.go
@@ -0,0 +1,25 @@
+package loadbalance
+
+import (
+ "github.com/mholt/caddy"
+ "github.com/miekg/coredns/core/dnsserver"
+)
+
+func init() {
+ caddy.RegisterPlugin("loadbalance", caddy.Plugin{
+ ServerType: "dns",
+ Action: setup,
+ })
+}
+
+func setup(c *caddy.Controller) error {
+ for c.Next() {
+ // TODO(miek): block and option parsing
+ }
+
+ dnsserver.GetConfig(c).AddMiddleware(func(next dnsserver.Handler) dnsserver.Handler {
+ return RoundRobin{Next: next}
+ })
+
+ return nil
+}
diff --git a/middleware/log/setup.go b/middleware/log/setup.go
new file mode 100644
index 000000000..a80e69ac3
--- /dev/null
+++ b/middleware/log/setup.go
@@ -0,0 +1,141 @@
+package log
+
+import (
+ "io"
+ "log"
+ "os"
+
+ "github.com/miekg/coredns/core/dnsserver"
+ "github.com/miekg/coredns/middleware"
+ "github.com/miekg/coredns/server"
+
+ "github.com/hashicorp/go-syslog"
+ "github.com/mholt/caddy"
+ "github.com/miekg/dns"
+)
+
+func init() {
+ caddy.RegisterPlugin("log", caddy.Plugin{
+ ServerType: "dns",
+ Action: setup,
+ })
+}
+
+func setup(c *caddy.Controller) error {
+ rules, err := logParse(c)
+ if err != nil {
+ return err
+ }
+
+ // Open the log files for writing when the server starts
+ c.OnStartup(func() error {
+ for i := 0; i < len(rules); i++ {
+ var err error
+ var writer io.Writer
+
+ if rules[i].OutputFile == "stdout" {
+ writer = os.Stdout
+ } else if rules[i].OutputFile == "stderr" {
+ writer = os.Stderr
+ } else if rules[i].OutputFile == "syslog" {
+ writer, err = gsyslog.NewLogger(gsyslog.LOG_INFO, "LOCAL0", "coredns")
+ if err != nil {
+ return err
+ }
+ } else {
+ var file *os.File
+ file, err = os.OpenFile(rules[i].OutputFile, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0644)
+ if err != nil {
+ return err
+ }
+ if rules[i].Roller != nil {
+ file.Close()
+ rules[i].Roller.Filename = rules[i].OutputFile
+ writer = rules[i].Roller.GetLogWriter()
+ } else {
+ writer = file
+ }
+ }
+
+ rules[i].Log = log.New(writer, "", 0)
+ }
+
+ return nil
+ })
+
+ dnsserver.GetConfig(c).AddMiddleware(func(next dnsserver.Handler) dnsserver.Handler {
+ return Logger{Next: next, Rules: rules, ErrorFunc: server.DefaultErrorFunc}
+ })
+
+ return nil
+}
+
+func logParse(c *caddy.Controller) ([]Rule, error) {
+ var rules []Rule
+
+ for c.Next() {
+ args := c.RemainingArgs()
+
+ var logRoller *middleware.LogRoller
+ if c.NextBlock() {
+ if c.Val() == "rotate" {
+ if c.NextArg() {
+ if c.Val() == "{" {
+ var err error
+ logRoller, err = middleware.ParseRoller(c)
+ if err != nil {
+ return nil, err
+ }
+ // This part doesn't allow having something after the rotate block
+ if c.Next() {
+ if c.Val() != "}" {
+ return nil, c.ArgErr()
+ }
+ }
+ }
+ }
+ }
+ }
+ if len(args) == 0 {
+ // Nothing specified; use defaults
+ rules = append(rules, Rule{
+ NameScope: ".",
+ OutputFile: DefaultLogFilename,
+ Format: DefaultLogFormat,
+ Roller: logRoller,
+ })
+ } else if len(args) == 1 {
+ // Only an output file specified
+ rules = append(rules, Rule{
+ NameScope: ".",
+ OutputFile: args[0],
+ Format: DefaultLogFormat,
+ Roller: logRoller,
+ })
+ } else {
+ // Name scope, output file, and maybe a format specified
+
+ format := DefaultLogFormat
+
+ if len(args) > 2 {
+ switch args[2] {
+ case "{common}":
+ format = CommonLogFormat
+ case "{combined}":
+ format = CombinedLogFormat
+ default:
+ format = args[2]
+ }
+ }
+
+ rules = append(rules, Rule{
+ NameScope: dns.Fqdn(args[0]),
+ OutputFile: args[1],
+ Format: format,
+ Roller: logRoller,
+ })
+ }
+ }
+
+ return rules, nil
+}
diff --git a/middleware/log/setup_test.go b/middleware/log/setup_test.go
new file mode 100644
index 000000000..0a3ee63fe
--- /dev/null
+++ b/middleware/log/setup_test.go
@@ -0,0 +1,137 @@
+package log
+
+import (
+ "testing"
+
+ "github.com/miekg/coredns/middleware"
+
+ "github.com/mholt/caddy"
+)
+
+func TestLogParse(t *testing.T) {
+ tests := []struct {
+ inputLogRules string
+ shouldErr bool
+ expectedLogRules []Rule
+ }{
+ {`log`, false, []Rule{{
+ NameScope: ".",
+ OutputFile: DefaultLogFilename,
+ Format: DefaultLogFormat,
+ }}},
+ {`log log.txt`, false, []Rule{{
+ NameScope: ".",
+ OutputFile: "log.txt",
+ Format: DefaultLogFormat,
+ }}},
+ {`log example.org log.txt`, false, []Rule{{
+ NameScope: "example.org.",
+ OutputFile: "log.txt",
+ Format: DefaultLogFormat,
+ }}},
+ {`log example.org. stdout`, false, []Rule{{
+ NameScope: "example.org.",
+ OutputFile: "stdout",
+ Format: DefaultLogFormat,
+ }}},
+ {`log example.org log.txt {common}`, false, []Rule{{
+ NameScope: "example.org.",
+ OutputFile: "log.txt",
+ Format: CommonLogFormat,
+ }}},
+ {`log example.org accesslog.txt {combined}`, false, []Rule{{
+ NameScope: "example.org.",
+ OutputFile: "accesslog.txt",
+ Format: CombinedLogFormat,
+ }}},
+ {`log example.org. log.txt
+ log example.net accesslog.txt {combined}`, false, []Rule{{
+ NameScope: "example.org.",
+ OutputFile: "log.txt",
+ Format: DefaultLogFormat,
+ }, {
+ NameScope: "example.net.",
+ OutputFile: "accesslog.txt",
+ Format: CombinedLogFormat,
+ }}},
+ {`log example.org stdout {host}
+ log example.org log.txt {when}`, false, []Rule{{
+ NameScope: "example.org.",
+ OutputFile: "stdout",
+ Format: "{host}",
+ }, {
+ NameScope: "example.org.",
+ OutputFile: "log.txt",
+ Format: "{when}",
+ }}},
+ {`log access.log { rotate { size 2 age 10 keep 3 } }`, false, []Rule{{
+ NameScope: ".",
+ OutputFile: "access.log",
+ Format: DefaultLogFormat,
+ Roller: &middleware.LogRoller{
+ MaxSize: 2,
+ MaxAge: 10,
+ MaxBackups: 3,
+ LocalTime: true,
+ },
+ }}},
+ }
+ for i, test := range tests {
+ c := caddy.NewTestController("dns", test.inputLogRules)
+ actualLogRules, err := logParse(c)
+
+ if err == nil && test.shouldErr {
+ t.Errorf("Test %d didn't error, but it should have", i)
+ } else if err != nil && !test.shouldErr {
+ t.Errorf("Test %d errored, but it shouldn't have; got '%v'", i, err)
+ }
+ if len(actualLogRules) != len(test.expectedLogRules) {
+ t.Fatalf("Test %d expected %d no of Log rules, but got %d ",
+ i, len(test.expectedLogRules), len(actualLogRules))
+ }
+ for j, actualLogRule := range actualLogRules {
+
+ if actualLogRule.NameScope != test.expectedLogRules[j].NameScope {
+ t.Errorf("Test %d expected %dth LogRule NameScope to be %s , but got %s",
+ i, j, test.expectedLogRules[j].NameScope, actualLogRule.NameScope)
+ }
+
+ if actualLogRule.OutputFile != test.expectedLogRules[j].OutputFile {
+ t.Errorf("Test %d expected %dth LogRule OutputFile to be %s , but got %s",
+ i, j, test.expectedLogRules[j].OutputFile, actualLogRule.OutputFile)
+ }
+
+ if actualLogRule.Format != test.expectedLogRules[j].Format {
+ t.Errorf("Test %d expected %dth LogRule Format to be %s , but got %s",
+ i, j, test.expectedLogRules[j].Format, actualLogRule.Format)
+ }
+ if actualLogRule.Roller != nil && test.expectedLogRules[j].Roller == nil || actualLogRule.Roller == nil && test.expectedLogRules[j].Roller != nil {
+ t.Fatalf("Test %d expected %dth LogRule Roller to be %v, but got %v",
+ i, j, test.expectedLogRules[j].Roller, actualLogRule.Roller)
+ }
+ if actualLogRule.Roller != nil && test.expectedLogRules[j].Roller != nil {
+ if actualLogRule.Roller.Filename != test.expectedLogRules[j].Roller.Filename {
+ t.Fatalf("Test %d expected %dth LogRule Roller Filename to be %s, but got %s",
+ i, j, test.expectedLogRules[j].Roller.Filename, actualLogRule.Roller.Filename)
+ }
+ if actualLogRule.Roller.MaxAge != test.expectedLogRules[j].Roller.MaxAge {
+ t.Fatalf("Test %d expected %dth LogRule Roller MaxAge to be %d, but got %d",
+ i, j, test.expectedLogRules[j].Roller.MaxAge, actualLogRule.Roller.MaxAge)
+ }
+ if actualLogRule.Roller.MaxBackups != test.expectedLogRules[j].Roller.MaxBackups {
+ t.Fatalf("Test %d expected %dth LogRule Roller MaxBackups to be %d, but got %d",
+ i, j, test.expectedLogRules[j].Roller.MaxBackups, actualLogRule.Roller.MaxBackups)
+ }
+ if actualLogRule.Roller.MaxSize != test.expectedLogRules[j].Roller.MaxSize {
+ t.Fatalf("Test %d expected %dth LogRule Roller MaxSize to be %d, but got %d",
+ i, j, test.expectedLogRules[j].Roller.MaxSize, actualLogRule.Roller.MaxSize)
+ }
+ if actualLogRule.Roller.LocalTime != test.expectedLogRules[j].Roller.LocalTime {
+ t.Fatalf("Test %d expected %dth LogRule Roller LocalTime to be %t, but got %t",
+ i, j, test.expectedLogRules[j].Roller.LocalTime, actualLogRule.Roller.LocalTime)
+ }
+ }
+ }
+ }
+
+}
diff --git a/middleware/metrics/metrics.go b/middleware/metrics/metrics.go
index 1c7db29d2..3a72bc3bb 100644
--- a/middleware/metrics/metrics.go
+++ b/middleware/metrics/metrics.go
@@ -34,7 +34,7 @@ type Metrics struct {
ZoneNames []string
}
-func (m *Metrics) Start() error {
+func (m *Metrics) Startup() error {
m.Once.Do(func() {
define()
diff --git a/middleware/metrics/setup.go b/middleware/metrics/setup.go
new file mode 100644
index 000000000..f31cbd4d5
--- /dev/null
+++ b/middleware/metrics/setup.go
@@ -0,0 +1,84 @@
+package metrics
+
+import (
+ "sync"
+
+ "github.com/miekg/coredns/core/dnsserver"
+ "github.com/miekg/coredns/middleware"
+
+ "github.com/mholt/caddy"
+)
+
+func init() {
+ caddy.RegisterPlugin("prometheus", caddy.Plugin{
+ ServerType: "dns",
+ Action: setup,
+ })
+}
+
+func setup(c *caddy.Controller) error {
+ m, err := prometheusParse(c)
+ if err != nil {
+ return err
+ }
+
+ dnsserver.GetConfig(c).AddMiddleware(func(next dnsserver.Handler) dnsserver.Handler {
+ m.Next = next
+ return m
+ })
+
+ metricsOnce.Do(func() {
+ c.OnStartup(m.Startup)
+ c.OnShutdown(m.Shutdown)
+ })
+
+ return nil
+}
+
+func prometheusParse(c *caddy.Controller) (Metrics, error) {
+ var (
+ met Metrics
+ err error
+ )
+
+ for c.Next() {
+ if len(met.ZoneNames) > 0 {
+ return Metrics{}, c.Err("metrics: can only have one metrics module per server")
+ }
+ met.ZoneNames = make([]string, len(c.ServerBlockKeys))
+ copy(met.ZoneNames, c.ServerBlockKeys)
+ for i, _ := range met.ZoneNames {
+ met.ZoneNames[i] = middleware.Host(met.ZoneNames[i]).Normalize()
+ }
+ args := c.RemainingArgs()
+
+ switch len(args) {
+ case 0:
+ case 1:
+ met.Addr = args[0]
+ default:
+ return Metrics{}, c.ArgErr()
+ }
+ for c.NextBlock() {
+ switch c.Val() {
+ case "address":
+ args = c.RemainingArgs()
+ if len(args) != 1 {
+ return Metrics{}, c.ArgErr()
+ }
+ met.Addr = args[0]
+ default:
+ return Metrics{}, c.Errf("metrics: unknown item: %s", c.Val())
+ }
+
+ }
+ }
+ if met.Addr == "" {
+ met.Addr = addr
+ }
+ return met, err
+}
+
+var metricsOnce sync.Once
+
+const addr = "localhost:9153"
diff --git a/middleware/pprof/pprof.go b/middleware/pprof/pprof.go
index 42102d198..f538b3091 100644
--- a/middleware/pprof/pprof.go
+++ b/middleware/pprof/pprof.go
@@ -12,7 +12,7 @@ type Handler struct {
mux *http.ServeMux
}
-func (h *Handler) Start() error {
+func (h *Handler) Startup() error {
if ln, err := net.Listen("tcp", addr); err != nil {
log.Printf("[ERROR] Failed to start pprof handler: %s", err)
return err
diff --git a/middleware/pprof/setup.go b/middleware/pprof/setup.go
new file mode 100644
index 000000000..2b6701f35
--- /dev/null
+++ b/middleware/pprof/setup.go
@@ -0,0 +1,40 @@
+package pprof
+
+import (
+ "sync"
+
+ "github.com/mholt/caddy"
+)
+
+func init() {
+ caddy.RegisterPlugin("pprof", caddy.Plugin{
+ ServerType: "dns",
+ Action: setup,
+ })
+}
+
+func setup(c *caddy.Controller) error {
+ found := false
+ for c.Next() {
+ if found {
+ return c.Err("pprof can only be specified once")
+ }
+ if len(c.RemainingArgs()) != 0 {
+ return c.ArgErr()
+ }
+ if c.NextBlock() {
+ return c.ArgErr()
+ }
+ found = true
+ }
+
+ handler := &Handler{}
+ pprofOnce.Do(func() {
+ c.OnStartup(handler.Startup)
+ c.OnShutdown(handler.Shutdown)
+ })
+
+ return nil
+}
+
+var pprofOnce sync.Once
diff --git a/middleware/pprof/setup_test.go b/middleware/pprof/setup_test.go
new file mode 100644
index 000000000..af46fd415
--- /dev/null
+++ b/middleware/pprof/setup_test.go
@@ -0,0 +1,32 @@
+package pprof
+
+import (
+ "testing"
+
+ "github.com/mholt/caddy"
+)
+
+func TestPProf(t *testing.T) {
+ tests := []struct {
+ input string
+ shouldErr bool
+ }{
+ {`pprof`, false},
+ {`pprof {}`, true},
+ {`pprof /foo`, true},
+ {`pprof {
+ a b
+ }`, true},
+ {`pprof
+ pprof`, true},
+ }
+ for i, test := range tests {
+ c := caddy.NewTestController("dns", test.input)
+ err := setup(c)
+ if test.shouldErr && err == nil {
+ t.Errorf("Test %v: Expected error but found nil", i)
+ } else if !test.shouldErr && err != nil {
+ t.Errorf("Test %v: Expected no error but found error: %v", i, err)
+ }
+ }
+}
diff --git a/middleware/proxy/setup.go b/middleware/proxy/setup.go
new file mode 100644
index 000000000..81dc7777c
--- /dev/null
+++ b/middleware/proxy/setup.go
@@ -0,0 +1,26 @@
+package proxy
+
+import (
+ "github.com/miekg/coredns/core/dnsserver"
+
+ "github.com/mholt/caddy"
+)
+
+func init() {
+ caddy.RegisterPlugin("proxy", caddy.Plugin{
+ ServerType: "dns",
+ Action: setup,
+ })
+}
+
+func setup(c *caddy.Controller) error {
+ upstreams, err := NewStaticUpstreams(c.Dispenser)
+ if err != nil {
+ return err
+ }
+ dnsserver.GetConfig(c).AddMiddleware(func(next dnsserver.Handler) dnsserver.Handler {
+ return Proxy{Next: next, Client: Clients(), Upstreams: upstreams}
+ })
+
+ return nil
+}
diff --git a/middleware/proxy/upstream.go b/middleware/proxy/upstream.go
index 76c27a45a..12ec00d76 100644
--- a/middleware/proxy/upstream.go
+++ b/middleware/proxy/upstream.go
@@ -11,8 +11,9 @@ import (
"sync/atomic"
"time"
- "github.com/miekg/coredns/core/parse"
"github.com/miekg/coredns/middleware"
+
+ "github.com/mholt/caddy/caddyfile"
"github.com/miekg/dns"
)
@@ -43,7 +44,7 @@ type Options struct {
// NewStaticUpstreams parses the configuration input and sets up
// static upstreams for the proxy middleware.
-func NewStaticUpstreams(c parse.Dispenser) ([]Upstream, error) {
+func NewStaticUpstreams(c caddyfile.Dispenser) ([]Upstream, error) {
var upstreams []Upstream
for c.Next() {
upstream := &staticUpstream{
@@ -73,7 +74,7 @@ func NewStaticUpstreams(c parse.Dispenser) ([]Upstream, error) {
}
for c.NextBlock() {
- if err := parseBlock(&c, upstream); err != nil {
+ if err := parseBlock(c, upstream); err != nil {
return upstreams, err
}
}
@@ -125,7 +126,7 @@ func (u *staticUpstream) Options() Options {
return u.options
}
-func parseBlock(c *parse.Dispenser, u *staticUpstream) error {
+func parseBlock(c caddyfile.Dispenser, u *staticUpstream) error {
switch c.Val() {
case "policy":
if !c.NextArg() {
diff --git a/middleware/rewrite/setup.go b/middleware/rewrite/setup.go
new file mode 100644
index 000000000..abfc0fbd6
--- /dev/null
+++ b/middleware/rewrite/setup.go
@@ -0,0 +1,115 @@
+package rewrite
+
+import (
+ "strconv"
+ "strings"
+
+ "github.com/miekg/coredns/core/dnsserver"
+
+ "github.com/mholt/caddy"
+)
+
+func init() {
+ caddy.RegisterPlugin("rewrite", caddy.Plugin{
+ ServerType: "dns",
+ Action: setup,
+ })
+}
+
+func setup(c *caddy.Controller) error {
+ rewrites, err := rewriteParse(c)
+ if err != nil {
+ return err
+ }
+
+ dnsserver.GetConfig(c).AddMiddleware(func(next dnsserver.Handler) dnsserver.Handler {
+ return Rewrite{Next: next, Rules: rewrites}
+ })
+
+ return nil
+}
+
+func rewriteParse(c *caddy.Controller) ([]Rule, error) {
+ var simpleRules []Rule
+ var regexpRules []Rule
+
+ for c.Next() {
+ var rule Rule
+ var err error
+ var base = "."
+ var pattern, to string
+ var status int
+ var ext []string
+
+ args := c.RemainingArgs()
+
+ var ifs []If
+
+ switch len(args) {
+ case 1:
+ base = args[0]
+ fallthrough
+ case 0:
+ for c.NextBlock() {
+ switch c.Val() {
+ case "r", "regexp":
+ if !c.NextArg() {
+ return nil, c.ArgErr()
+ }
+ pattern = c.Val()
+ case "to":
+ args1 := c.RemainingArgs()
+ if len(args1) == 0 {
+ return nil, c.ArgErr()
+ }
+ to = strings.Join(args1, " ")
+ case "ext": // TODO(miek): fix or remove
+ args1 := c.RemainingArgs()
+ if len(args1) == 0 {
+ return nil, c.ArgErr()
+ }
+ ext = args1
+ case "if":
+ args1 := c.RemainingArgs()
+ if len(args1) != 3 {
+ return nil, c.ArgErr()
+ }
+ ifCond, err := NewIf(args1[0], args1[1], args1[2])
+ if err != nil {
+ return nil, err
+ }
+ ifs = append(ifs, ifCond)
+ case "status": // TODO(miek): fix or remove
+ if !c.NextArg() {
+ return nil, c.ArgErr()
+ }
+ status, _ = strconv.Atoi(c.Val())
+ if status < 200 || (status > 299 && status < 400) || status > 499 {
+ return nil, c.Err("status must be 2xx or 4xx")
+ }
+ default:
+ return nil, c.ArgErr()
+ }
+ }
+ // ensure to or status is specified
+ if to == "" && status == 0 {
+ return nil, c.ArgErr()
+ }
+ // TODO(miek): complex rules
+ base, pattern, to, status, ext, ifs = base, pattern, to, status, ext, ifs
+ err = err
+ // if rule, err = NewComplexRule(base, pattern, to, status, ext, ifs); err != nil {
+ // return nil, err
+ // }
+ regexpRules = append(regexpRules, rule)
+
+ // the only unhandled case is 2 and above
+ default:
+ rule = NewSimpleRule(args[0], strings.Join(args[1:], " "))
+ simpleRules = append(simpleRules, rule)
+ }
+ }
+
+ // put simple rules in front to avoid regexp computation for them
+ return append(simpleRules, regexpRules...), nil
+}
diff --git a/middleware/roller.go b/middleware/roller.go
index 995cabf91..81ff71c44 100644
--- a/middleware/roller.go
+++ b/middleware/roller.go
@@ -2,10 +2,45 @@ package middleware
import (
"io"
+ "strconv"
+ "github.com/mholt/caddy"
"gopkg.in/natefinch/lumberjack.v2"
)
+func ParseRoller(c *caddy.Controller) (*LogRoller, error) {
+ var size, age, keep int
+ // This is kind of a hack to support nested blocks:
+ // As we are already in a block: either log or errors,
+ // c.nesting > 0 but, as soon as c meets a }, it thinks
+ // the block is over and return false for c.NextBlock.
+ for c.NextBlock() {
+ what := c.Val()
+ if !c.NextArg() {
+ return nil, c.ArgErr()
+ }
+ value := c.Val()
+ var err error
+ switch what {
+ case "size":
+ size, err = strconv.Atoi(value)
+ case "age":
+ age, err = strconv.Atoi(value)
+ case "keep":
+ keep, err = strconv.Atoi(value)
+ }
+ if err != nil {
+ return nil, err
+ }
+ }
+ return &LogRoller{
+ MaxSize: size,
+ MaxAge: age,
+ MaxBackups: keep,
+ LocalTime: true,
+ }, nil
+}
+
// LogRoller implements a middleware that provides a rolling logger.
type LogRoller struct {
Filename string
diff --git a/middleware/secondary/setup.go b/middleware/secondary/setup.go
new file mode 100644
index 000000000..5550cf11d
--- /dev/null
+++ b/middleware/secondary/setup.go
@@ -0,0 +1,82 @@
+package secondary
+
+import (
+ "github.com/miekg/coredns/core/dnsserver"
+ "github.com/miekg/coredns/middleware"
+ "github.com/miekg/coredns/middleware/file"
+
+ "github.com/mholt/caddy"
+)
+
+func init() {
+ caddy.RegisterPlugin("secondary", caddy.Plugin{
+ ServerType: "dns",
+ Action: setup,
+ })
+}
+
+func setup(c *caddy.Controller) error {
+ zones, err := secondaryParse(c)
+ if err != nil {
+ return err
+ }
+
+ // Add startup functions to retrieve the zone and keep it up to date.
+ for _, n := range zones.Names {
+ if len(zones.Z[n].TransferFrom) > 0 {
+ c.OnStartup(func() error {
+ zones.Z[n].StartupOnce.Do(func() {
+ zones.Z[n].TransferIn()
+ go func() {
+ zones.Z[n].Update()
+ }()
+ })
+ return nil
+ })
+ }
+ }
+
+ dnsserver.GetConfig(c).AddMiddleware(func(next dnsserver.Handler) dnsserver.Handler {
+ return Secondary{file.File{Next: next, Zones: zones}}
+ })
+
+ return nil
+}
+
+func secondaryParse(c *caddy.Controller) (file.Zones, error) {
+ z := make(map[string]*file.Zone)
+ names := []string{}
+ origins := []string{}
+ for c.Next() {
+ if c.Val() == "secondary" {
+ // secondary [origin]
+ origins = make([]string, len(c.ServerBlockKeys))
+ copy(origins, c.ServerBlockKeys)
+ args := c.RemainingArgs()
+ if len(args) > 0 {
+ origins = args
+ }
+ for i, _ := range origins {
+ origins[i] = middleware.Host(origins[i]).Normalize()
+ z[origins[i]] = file.NewZone(origins[i], "stdin")
+ names = append(names, origins[i])
+ }
+
+ for c.NextBlock() {
+ t, f, e := file.TransferParse(c)
+ if e != nil {
+ return file.Zones{}, e
+ }
+ for _, origin := range origins {
+ if t != nil {
+ z[origin].TransferTo = append(z[origin].TransferTo, t...)
+ }
+ if f != nil {
+ z[origin].TransferFrom = append(z[origin].TransferFrom, f...)
+ }
+ }
+ }
+ }
+ }
+ return file.Zones{Z: z, Names: names}, nil
+}