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:
Miek Gieben
2016-08-19 17:14:17 -07:00
committed by GitHub
parent a1989c3523
commit 9ac3cab1b7
140 changed files with 2058 additions and 8229 deletions

21
middleware/bind/README.md Normal file
View 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
View File

@@ -0,0 +1,10 @@
package bind
import "github.com/mholt/caddy"
func init() {
caddy.RegisterPlugin("bind", caddy.Plugin{
ServerType: "dns",
Action: setupBind,
})
}

View 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
View 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
View 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
View 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"

View 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")
}
}
}
}

View File

@@ -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)

View File

@@ -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,

View File

@@ -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
}

View File

@@ -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 {

View 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
}

View 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
View 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
}

View 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)
}
}
}
}

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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
View 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"

View File

@@ -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

View File

@@ -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
View 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
}

View File

@@ -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
}

View 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
}

View File

@@ -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
}

View File

@@ -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")

View File

@@ -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)

View File

@@ -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
}

View 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
)

View 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)
}
}
}
}

View 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
View 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
}

View 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)
}
}
}
}
}

View File

@@ -34,7 +34,7 @@ type Metrics struct {
ZoneNames []string
}
func (m *Metrics) Start() error {
func (m *Metrics) Startup() error {
m.Once.Do(func() {
define()

View 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"

View File

@@ -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
View 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

View 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
View 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
}

View File

@@ -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
View 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
}

View File

@@ -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

View 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
}