aboutsummaryrefslogtreecommitdiff
path: root/middleware
diff options
context:
space:
mode:
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
+}