mirror of
https://github.com/coredns/coredns.git
synced 2026-04-25 21:35:33 -04:00
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.
This commit is contained in:
21
middleware/bind/README.md
Normal file
21
middleware/bind/README.md
Normal file
@@ -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
|
||||
~~~
|
||||
10
middleware/bind/bind.go
Normal file
10
middleware/bind/bind.go
Normal file
@@ -0,0 +1,10 @@
|
||||
package bind
|
||||
|
||||
import "github.com/mholt/caddy"
|
||||
|
||||
func init() {
|
||||
caddy.RegisterPlugin("bind", caddy.Plugin{
|
||||
ServerType: "dns",
|
||||
Action: setupBind,
|
||||
})
|
||||
}
|
||||
30
middleware/bind/bind_test.go
Normal file
30
middleware/bind/bind_test.go
Normal file
@@ -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")
|
||||
}
|
||||
}
|
||||
23
middleware/bind/setup.go
Normal file
23
middleware/bind/setup.go
Normal file
@@ -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
|
||||
}
|
||||
66
middleware/cache/setup.go
vendored
Normal file
66
middleware/cache/setup.go
vendored
Normal file
@@ -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
|
||||
}
|
||||
50
middleware/chaos/setup.go
Normal file
50
middleware/chaos/setup.go
Normal file
@@ -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"
|
||||
63
middleware/chaos/setup_test.go
Normal file
63
middleware/chaos/setup_test.go
Normal file
@@ -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")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
91
middleware/dnssec/setup.go
Normal file
91
middleware/dnssec/setup.go
Normal file
@@ -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
|
||||
}
|
||||
56
middleware/dnssec/setup_test.go
Normal file
56
middleware/dnssec/setup_test.go
Normal file
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
133
middleware/errors/setup.go
Normal file
133
middleware/errors/setup.go
Normal file
@@ -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
|
||||
}
|
||||
98
middleware/errors/setup_test.go
Normal file
98
middleware/errors/setup_test.go
Normal file
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
207
middleware/etcd/setup.go
Normal file
207
middleware/etcd/setup.go
Normal file
@@ -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"
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
143
middleware/file/setup.go
Normal file
143
middleware/file/setup.go
Normal file
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
42
middleware/health/setup.go
Normal file
42
middleware/health/setup.go
Normal file
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
140
middleware/kubernetes/setup.go
Normal file
140
middleware/kubernetes/setup.go
Normal file
@@ -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
|
||||
)
|
||||
390
middleware/kubernetes/setup_test.go
Normal file
390
middleware/kubernetes/setup_test.go
Normal file
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
25
middleware/loadbalance/setup.go
Normal file
25
middleware/loadbalance/setup.go
Normal file
@@ -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
|
||||
}
|
||||
141
middleware/log/setup.go
Normal file
141
middleware/log/setup.go
Normal file
@@ -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
|
||||
}
|
||||
137
middleware/log/setup_test.go
Normal file
137
middleware/log/setup_test.go
Normal file
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -34,7 +34,7 @@ type Metrics struct {
|
||||
ZoneNames []string
|
||||
}
|
||||
|
||||
func (m *Metrics) Start() error {
|
||||
func (m *Metrics) Startup() error {
|
||||
m.Once.Do(func() {
|
||||
define()
|
||||
|
||||
|
||||
84
middleware/metrics/setup.go
Normal file
84
middleware/metrics/setup.go
Normal file
@@ -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"
|
||||
@@ -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
|
||||
|
||||
40
middleware/pprof/setup.go
Normal file
40
middleware/pprof/setup.go
Normal file
@@ -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
|
||||
32
middleware/pprof/setup_test.go
Normal file
32
middleware/pprof/setup_test.go
Normal file
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
26
middleware/proxy/setup.go
Normal file
26
middleware/proxy/setup.go
Normal file
@@ -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
|
||||
}
|
||||
@@ -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() {
|
||||
|
||||
115
middleware/rewrite/setup.go
Normal file
115
middleware/rewrite/setup.go
Normal file
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
|
||||
82
middleware/secondary/setup.go
Normal file
82
middleware/secondary/setup.go
Normal file
@@ -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
|
||||
}
|
||||
Reference in New Issue
Block a user