First commit

This commit is contained in:
Miek Gieben
2016-03-18 20:57:35 +00:00
commit 3ec0d9fe6b
131 changed files with 15193 additions and 0 deletions

13
core/setup/bindhost.go Normal file
View File

@@ -0,0 +1,13 @@
package setup
import "github.com/miekg/coredns/middleware"
// BindHost sets the host to bind the listener to.
func BindHost(c *Controller) (middleware.Middleware, error) {
for c.Next() {
if !c.Args(&c.BindHost) {
return nil, c.ArgErr()
}
}
return nil, nil
}

83
core/setup/controller.go Normal file
View File

@@ -0,0 +1,83 @@
package setup
import (
"fmt"
"strings"
"github.com/miekg/coredns/core/parse"
"github.com/miekg/coredns/middleware"
"github.com/miekg/coredns/server"
"github.com/miekg/dns"
)
// Controller is given to the setup function of middlewares which
// gives them access to be able to read tokens and set config. Each
// virtualhost gets their own server config and dispenser.
type Controller struct {
*server.Config
parse.Dispenser
// OncePerServerBlock is a function that executes f
// exactly once per server block, no matter how many
// hosts are associated with it. If it is the first
// time, the function f is executed immediately
// (not deferred) and may return an error which is
// returned by OncePerServerBlock.
OncePerServerBlock func(f func() error) error
// ServerBlockIndex is the 0-based index of the
// server block as it appeared in the input.
ServerBlockIndex int
// ServerBlockHostIndex is the 0-based index of this
// host as it appeared in the input at the head of the
// server block.
ServerBlockHostIndex int
// ServerBlockHosts is a list of hosts that are
// associated with this server block. All these
// hosts, consequently, share the same tokens.
ServerBlockHosts []string
// ServerBlockStorage is used by a directive's
// setup function to persist state between all
// the hosts on a server block.
ServerBlockStorage interface{}
}
// NewTestController creates a new *Controller for
// the input specified, with a filename of "Testfile".
// The Config is bare, consisting only of a Root of cwd.
//
// Used primarily for testing but needs to be exported so
// add-ons can use this as a convenience. Does not initialize
// the server-block-related fields.
func NewTestController(input string) *Controller {
return &Controller{
Config: &server.Config{
Root: ".",
},
Dispenser: parse.NewDispenser("Testfile", strings.NewReader(input)),
OncePerServerBlock: func(f func() error) error {
return f()
},
}
}
// EmptyNext is a no-op function that can be passed into
// middleware.Middleware functions so that the assignment
// to the Next field of the Handler can be tested.
//
// Used primarily for testing but needs to be exported so
// add-ons can use this as a convenience.
var EmptyNext = middleware.HandlerFunc(func(w dns.ResponseWriter, r *dns.Msg) (int, error) {
return 0, nil
})
// SameNext does a pointer comparison between next1 and next2.
//
// Used primarily for testing but needs to be exported so
// add-ons can use this as a convenience.
func SameNext(next1, next2 middleware.Handler) bool {
return fmt.Sprintf("%v", next1) == fmt.Sprintf("%v", next2)
}

132
core/setup/errors.go Normal file
View File

@@ -0,0 +1,132 @@
package setup
import (
"io"
"log"
"os"
"github.com/hashicorp/go-syslog"
"github.com/miekg/coredns/middleware"
"github.com/miekg/coredns/middleware/errors"
)
// Errors configures a new errors middleware instance.
func Errors(c *Controller) (middleware.Middleware, error) {
handler, err := errorsParse(c)
if err != nil {
return nil, err
}
// Open the log file for writing when the server starts
c.Startup = append(c.Startup, func() error {
var err error
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", "caddy")
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)
return nil
})
return func(next middleware.Handler) middleware.Handler {
handler.Next = next
return handler
}, nil
}
func errorsParse(c *Controller) (*errors.ErrorHandler, error) {
// Very important that we make a pointer because the Startup
// function that opens the log file must have access to the
// same instance of the handler, not a copy.
handler := &errors.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 := 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
}

158
core/setup/errors_test.go Normal file
View File

@@ -0,0 +1,158 @@
package setup
import (
"testing"
"github.com/miekg/coredns/middleware"
"github.com/miekg/coredns/middleware/errors"
)
func TestErrors(t *testing.T) {
c := NewTestController(`errors`)
mid, err := Errors(c)
if err != nil {
t.Errorf("Expected no errors, got: %v", err)
}
if mid == nil {
t.Fatal("Expected middleware, was nil instead")
}
handler := mid(EmptyNext)
myHandler, ok := handler.(*errors.ErrorHandler)
if !ok {
t.Fatalf("Expected handler to be type ErrorHandler, got: %#v", handler)
}
if myHandler.LogFile != "" {
t.Errorf("Expected '%s' as the default LogFile", "")
}
if myHandler.LogRoller != nil {
t.Errorf("Expected LogRoller to be nil, got: %v", *myHandler.LogRoller)
}
if !SameNext(myHandler.Next, EmptyNext) {
t.Error("'Next' field of handler was not set properly")
}
// Test Startup function
if len(c.Startup) == 0 {
t.Fatal("Expected 1 startup function, had 0")
}
err = c.Startup[0]()
if myHandler.Log == nil {
t.Error("Expected Log to be non-nil after startup because Debug is not enabled")
}
}
func TestErrorsParse(t *testing.T) {
tests := []struct {
inputErrorsRules string
shouldErr bool
expectedErrorHandler errors.ErrorHandler
}{
{`errors`, false, errors.ErrorHandler{
LogFile: "",
}},
{`errors errors.txt`, false, errors.ErrorHandler{
LogFile: "errors.txt",
}},
{`errors visible`, false, errors.ErrorHandler{
LogFile: "",
Debug: true,
}},
{`errors { log visible }`, false, errors.ErrorHandler{
LogFile: "",
Debug: true,
}},
{`errors { log errors.txt
404 404.html
500 500.html
}`, false, errors.ErrorHandler{
LogFile: "errors.txt",
ErrorPages: map[int]string{
404: "404.html",
500: "500.html",
},
}},
{`errors { log errors.txt { size 2 age 10 keep 3 } }`, false, errors.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
}
404 404.html
503 503.html
}`, false, errors.ErrorHandler{
LogFile: "errors.txt",
ErrorPages: map[int]string{
404: "404.html",
503: "503.html",
},
LogRoller: &middleware.LogRoller{
MaxSize: 3,
MaxAge: 11,
MaxBackups: 5,
LocalTime: true,
},
}},
}
for i, test := range tests {
c := NewTestController(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 len(actualErrorsRule.ErrorPages) != len(test.expectedErrorHandler.ErrorPages) {
t.Fatalf("Test %d expected %d no of Error pages, but got %d ",
i, len(test.expectedErrorHandler.ErrorPages), len(actualErrorsRule.ErrorPages))
}
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)
}
}
}
}

73
core/setup/file.go Normal file
View File

@@ -0,0 +1,73 @@
package setup
import (
"log"
"os"
"github.com/miekg/coredns/middleware"
"github.com/miekg/coredns/middleware/file"
"github.com/miekg/dns"
)
// File sets up the file middleware.
func File(c *Controller) (middleware.Middleware, error) {
zones, err := fileParse(c)
if err != nil {
return nil, err
}
return func(next middleware.Handler) middleware.Handler {
return file.File{Next: next, Zones: zones}
}, nil
}
func fileParse(c *Controller) (file.Zones, error) {
// Maybe multiple, each for each zone.
z := make(map[string]file.Zone)
names := []string{}
for c.Next() {
if c.Val() == "file" {
// file db.file [origin]
if !c.NextArg() {
return file.Zones{}, c.ArgErr()
}
fileName := c.Val()
origin := c.ServerBlockHosts[c.ServerBlockHostIndex]
if c.NextArg() {
c.Next()
origin = c.Val()
}
// normalize this origin
origin = middleware.Host(origin).StandardHost()
zone, err := parseZone(origin, fileName)
if err == nil {
z[origin] = zone
}
names = append(names, origin)
}
}
return file.Zones{Z: z, Names: names}, nil
}
//
// parsrZone parses the zone in filename and returns a []RR or an error.
func parseZone(origin, fileName string) (file.Zone, error) {
f, err := os.Open(fileName)
if err != nil {
return nil, err
}
tokens := dns.ParseZone(f, origin, fileName)
zone := make([]dns.RR, 0, defaultZoneSize)
for x := range tokens {
if x.Error != nil {
log.Printf("[ERROR] failed to parse %s: %v", origin, x.Error)
return nil, x.Error
}
zone = append(zone, x.RR)
}
return file.Zone(zone), nil
}
const defaultZoneSize = 20 // A made up number.

130
core/setup/log.go Normal file
View File

@@ -0,0 +1,130 @@
package setup
import (
"io"
"log"
"os"
"github.com/hashicorp/go-syslog"
"github.com/miekg/coredns/middleware"
caddylog "github.com/miekg/coredns/middleware/log"
"github.com/miekg/coredns/server"
)
// Log sets up the logging middleware.
func Log(c *Controller) (middleware.Middleware, error) {
rules, err := logParse(c)
if err != nil {
return nil, err
}
// Open the log files for writing when the server starts
c.Startup = append(c.Startup, 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", "caddy")
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
})
return func(next middleware.Handler) middleware.Handler {
return caddylog.Logger{Next: next, Rules: rules, ErrorFunc: server.DefaultErrorFunc}
}, nil
}
func logParse(c *Controller) ([]caddylog.Rule, error) {
var rules []caddylog.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 = 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, caddylog.Rule{
PathScope: "/",
OutputFile: caddylog.DefaultLogFilename,
Format: caddylog.DefaultLogFormat,
Roller: logRoller,
})
} else if len(args) == 1 {
// Only an output file specified
rules = append(rules, caddylog.Rule{
PathScope: "/",
OutputFile: args[0],
Format: caddylog.DefaultLogFormat,
Roller: logRoller,
})
} else {
// Path scope, output file, and maybe a format specified
format := caddylog.DefaultLogFormat
if len(args) > 2 {
switch args[2] {
case "{common}":
format = caddylog.CommonLogFormat
case "{combined}":
format = caddylog.CombinedLogFormat
default:
format = args[2]
}
}
rules = append(rules, caddylog.Rule{
PathScope: args[0],
OutputFile: args[1],
Format: format,
Roller: logRoller,
})
}
}
return rules, nil
}

175
core/setup/log_test.go Normal file
View File

@@ -0,0 +1,175 @@
package setup
import (
"testing"
"github.com/miekg/coredns/middleware"
caddylog "github.com/miekg/coredns/middleware/log"
)
func TestLog(t *testing.T) {
c := NewTestController(`log`)
mid, err := Log(c)
if err != nil {
t.Errorf("Expected no errors, got: %v", err)
}
if mid == nil {
t.Fatal("Expected middleware, was nil instead")
}
handler := mid(EmptyNext)
myHandler, ok := handler.(caddylog.Logger)
if !ok {
t.Fatalf("Expected handler to be type Logger, got: %#v", handler)
}
if myHandler.Rules[0].PathScope != "/" {
t.Errorf("Expected / as the default PathScope")
}
if myHandler.Rules[0].OutputFile != caddylog.DefaultLogFilename {
t.Errorf("Expected %s as the default OutputFile", caddylog.DefaultLogFilename)
}
if myHandler.Rules[0].Format != caddylog.DefaultLogFormat {
t.Errorf("Expected %s as the default Log Format", caddylog.DefaultLogFormat)
}
if myHandler.Rules[0].Roller != nil {
t.Errorf("Expected Roller to be nil, got: %v", *myHandler.Rules[0].Roller)
}
if !SameNext(myHandler.Next, EmptyNext) {
t.Error("'Next' field of handler was not set properly")
}
}
func TestLogParse(t *testing.T) {
tests := []struct {
inputLogRules string
shouldErr bool
expectedLogRules []caddylog.Rule
}{
{`log`, false, []caddylog.Rule{{
PathScope: "/",
OutputFile: caddylog.DefaultLogFilename,
Format: caddylog.DefaultLogFormat,
}}},
{`log log.txt`, false, []caddylog.Rule{{
PathScope: "/",
OutputFile: "log.txt",
Format: caddylog.DefaultLogFormat,
}}},
{`log /api log.txt`, false, []caddylog.Rule{{
PathScope: "/api",
OutputFile: "log.txt",
Format: caddylog.DefaultLogFormat,
}}},
{`log /serve stdout`, false, []caddylog.Rule{{
PathScope: "/serve",
OutputFile: "stdout",
Format: caddylog.DefaultLogFormat,
}}},
{`log /myapi log.txt {common}`, false, []caddylog.Rule{{
PathScope: "/myapi",
OutputFile: "log.txt",
Format: caddylog.CommonLogFormat,
}}},
{`log /test accesslog.txt {combined}`, false, []caddylog.Rule{{
PathScope: "/test",
OutputFile: "accesslog.txt",
Format: caddylog.CombinedLogFormat,
}}},
{`log /api1 log.txt
log /api2 accesslog.txt {combined}`, false, []caddylog.Rule{{
PathScope: "/api1",
OutputFile: "log.txt",
Format: caddylog.DefaultLogFormat,
}, {
PathScope: "/api2",
OutputFile: "accesslog.txt",
Format: caddylog.CombinedLogFormat,
}}},
{`log /api3 stdout {host}
log /api4 log.txt {when}`, false, []caddylog.Rule{{
PathScope: "/api3",
OutputFile: "stdout",
Format: "{host}",
}, {
PathScope: "/api4",
OutputFile: "log.txt",
Format: "{when}",
}}},
{`log access.log { rotate { size 2 age 10 keep 3 } }`, false, []caddylog.Rule{{
PathScope: "/",
OutputFile: "access.log",
Format: caddylog.DefaultLogFormat,
Roller: &middleware.LogRoller{
MaxSize: 2,
MaxAge: 10,
MaxBackups: 3,
LocalTime: true,
},
}}},
}
for i, test := range tests {
c := NewTestController(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.PathScope != test.expectedLogRules[j].PathScope {
t.Errorf("Test %d expected %dth LogRule PathScope to be %s , but got %s",
i, j, test.expectedLogRules[j].PathScope, actualLogRule.PathScope)
}
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)
}
}
}
}
}

70
core/setup/prometheus.go Normal file
View File

@@ -0,0 +1,70 @@
package setup
import (
"sync"
"github.com/miekg/coredns/middleware"
prom "github.com/miekg/coredns/middleware/prometheus"
)
const (
path = "/metrics"
addr = "localhost:9153"
)
var once sync.Once
func Prometheus(c *Controller) (middleware.Middleware, error) {
metrics, err := parsePrometheus(c)
if err != nil {
return nil, err
}
if metrics.Addr == "" {
metrics.Addr = addr
}
once.Do(func() {
c.Startup = append(c.Startup, metrics.Start)
})
return func(next middleware.Handler) middleware.Handler {
metrics.Next = next
return metrics
}, nil
}
func parsePrometheus(c *Controller) (*prom.Metrics, error) {
var (
metrics *prom.Metrics
err error
)
for c.Next() {
if metrics != nil {
return nil, c.Err("prometheus: can only have one metrics module per server")
}
metrics = &prom.Metrics{ZoneNames: c.ServerBlockHosts}
args := c.RemainingArgs()
switch len(args) {
case 0:
case 1:
metrics.Addr = args[0]
default:
return nil, c.ArgErr()
}
for c.NextBlock() {
switch c.Val() {
case "address":
args = c.RemainingArgs()
if len(args) != 1 {
return nil, c.ArgErr()
}
metrics.Addr = args[0]
default:
return nil, c.Errf("prometheus: unknown item: %s", c.Val())
}
}
}
return metrics, err
}

17
core/setup/proxy.go Normal file
View File

@@ -0,0 +1,17 @@
package setup
import (
"github.com/miekg/coredns/middleware"
"github.com/miekg/coredns/middleware/proxy"
)
// Proxy configures a new Proxy middleware instance.
func Proxy(c *Controller) (middleware.Middleware, error) {
upstreams, err := proxy.NewStaticUpstreams(c.Dispenser)
if err != nil {
return nil, err
}
return func(next middleware.Handler) middleware.Handler {
return proxy.Proxy{Next: next, Client: proxy.Clients(), Upstreams: upstreams}
}, nil
}

28
core/setup/reflect.go Normal file
View File

@@ -0,0 +1,28 @@
package setup
import (
"github.com/miekg/coredns/middleware"
"github.com/miekg/coredns/middleware/reflect"
)
// Reflect sets up the reflect middleware.
func Reflect(c *Controller) (middleware.Middleware, error) {
if err := reflectParse(c); err != nil {
return nil, err
}
return func(next middleware.Handler) middleware.Handler {
return reflect.Reflect{Next: next}
}, nil
}
func reflectParse(c *Controller) error {
for c.Next() {
if c.Val() == "reflect" {
if c.NextArg() {
return c.ArgErr()
}
}
}
return nil
}

109
core/setup/rewrite.go Normal file
View File

@@ -0,0 +1,109 @@
package setup
import (
"strconv"
"strings"
"github.com/miekg/coredns/middleware"
"github.com/miekg/coredns/middleware/rewrite"
)
// Rewrite configures a new Rewrite middleware instance.
func Rewrite(c *Controller) (middleware.Middleware, error) {
rewrites, err := rewriteParse(c)
if err != nil {
return nil, err
}
return func(next middleware.Handler) middleware.Handler {
return rewrite.Rewrite{
Next: next,
Rules: rewrites,
}
}, nil
}
func rewriteParse(c *Controller) ([]rewrite.Rule, error) {
var simpleRules []rewrite.Rule
var regexpRules []rewrite.Rule
for c.Next() {
var rule rewrite.Rule
var err error
var base = "/"
var pattern, to string
var status int
var ext []string
args := c.RemainingArgs()
var ifs []rewrite.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":
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 := rewrite.NewIf(args1[0], args1[1], args1[2])
if err != nil {
return nil, err
}
ifs = append(ifs, ifCond)
case "status":
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 = rewrite.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 = rewrite.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
}

241
core/setup/rewrite_test.go Normal file
View File

@@ -0,0 +1,241 @@
package setup
import (
"fmt"
"regexp"
"testing"
"github.com/miekg/coredns/middleware/rewrite"
)
func TestRewrite(t *testing.T) {
c := NewTestController(`rewrite /from /to`)
mid, err := Rewrite(c)
if err != nil {
t.Errorf("Expected no errors, but got: %v", err)
}
if mid == nil {
t.Fatal("Expected middleware, was nil instead")
}
handler := mid(EmptyNext)
myHandler, ok := handler.(rewrite.Rewrite)
if !ok {
t.Fatalf("Expected handler to be type Rewrite, got: %#v", handler)
}
if !SameNext(myHandler.Next, EmptyNext) {
t.Error("'Next' field of handler was not set properly")
}
if len(myHandler.Rules) != 1 {
t.Errorf("Expected handler to have %d rule, has %d instead", 1, len(myHandler.Rules))
}
}
func TestRewriteParse(t *testing.T) {
simpleTests := []struct {
input string
shouldErr bool
expected []rewrite.Rule
}{
{`rewrite /from /to`, false, []rewrite.Rule{
rewrite.SimpleRule{From: "/from", To: "/to"},
}},
{`rewrite /from /to
rewrite a b`, false, []rewrite.Rule{
rewrite.SimpleRule{From: "/from", To: "/to"},
rewrite.SimpleRule{From: "a", To: "b"},
}},
{`rewrite a`, true, []rewrite.Rule{}},
{`rewrite`, true, []rewrite.Rule{}},
{`rewrite a b c`, false, []rewrite.Rule{
rewrite.SimpleRule{From: "a", To: "b c"},
}},
}
for i, test := range simpleTests {
c := NewTestController(test.input)
actual, err := rewriteParse(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)
} else if err != nil && test.shouldErr {
continue
}
if len(actual) != len(test.expected) {
t.Fatalf("Test %d expected %d rules, but got %d",
i, len(test.expected), len(actual))
}
for j, e := range test.expected {
actualRule := actual[j].(rewrite.SimpleRule)
expectedRule := e.(rewrite.SimpleRule)
if actualRule.From != expectedRule.From {
t.Errorf("Test %d, rule %d: Expected From=%s, got %s",
i, j, expectedRule.From, actualRule.From)
}
if actualRule.To != expectedRule.To {
t.Errorf("Test %d, rule %d: Expected To=%s, got %s",
i, j, expectedRule.To, actualRule.To)
}
}
}
regexpTests := []struct {
input string
shouldErr bool
expected []rewrite.Rule
}{
{`rewrite {
r .*
to /to /index.php?
}`, false, []rewrite.Rule{
&rewrite.ComplexRule{Base: "/", To: "/to /index.php?", Regexp: regexp.MustCompile(".*")},
}},
{`rewrite {
regexp .*
to /to
ext / html txt
}`, false, []rewrite.Rule{
&rewrite.ComplexRule{Base: "/", To: "/to", Exts: []string{"/", "html", "txt"}, Regexp: regexp.MustCompile(".*")},
}},
{`rewrite /path {
r rr
to /dest
}
rewrite / {
regexp [a-z]+
to /to /to2
}
`, false, []rewrite.Rule{
&rewrite.ComplexRule{Base: "/path", To: "/dest", Regexp: regexp.MustCompile("rr")},
&rewrite.ComplexRule{Base: "/", To: "/to /to2", Regexp: regexp.MustCompile("[a-z]+")},
}},
{`rewrite {
r .*
}`, true, []rewrite.Rule{
&rewrite.ComplexRule{},
}},
{`rewrite {
}`, true, []rewrite.Rule{
&rewrite.ComplexRule{},
}},
{`rewrite /`, true, []rewrite.Rule{
&rewrite.ComplexRule{},
}},
{`rewrite {
to /to
if {path} is a
}`, false, []rewrite.Rule{
&rewrite.ComplexRule{Base: "/", To: "/to", Ifs: []rewrite.If{{A: "{path}", Operator: "is", B: "a"}}},
}},
{`rewrite {
status 500
}`, true, []rewrite.Rule{
&rewrite.ComplexRule{},
}},
{`rewrite {
status 400
}`, false, []rewrite.Rule{
&rewrite.ComplexRule{Base: "/", Status: 400},
}},
{`rewrite {
to /to
status 400
}`, false, []rewrite.Rule{
&rewrite.ComplexRule{Base: "/", To: "/to", Status: 400},
}},
{`rewrite {
status 399
}`, true, []rewrite.Rule{
&rewrite.ComplexRule{},
}},
{`rewrite {
status 200
}`, false, []rewrite.Rule{
&rewrite.ComplexRule{Base: "/", Status: 200},
}},
{`rewrite {
to /to
status 200
}`, false, []rewrite.Rule{
&rewrite.ComplexRule{Base: "/", To: "/to", Status: 200},
}},
{`rewrite {
status 199
}`, true, []rewrite.Rule{
&rewrite.ComplexRule{},
}},
{`rewrite {
status 0
}`, true, []rewrite.Rule{
&rewrite.ComplexRule{},
}},
{`rewrite {
to /to
status 0
}`, true, []rewrite.Rule{
&rewrite.ComplexRule{},
}},
}
for i, test := range regexpTests {
c := NewTestController(test.input)
actual, err := rewriteParse(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)
} else if err != nil && test.shouldErr {
continue
}
if len(actual) != len(test.expected) {
t.Fatalf("Test %d expected %d rules, but got %d",
i, len(test.expected), len(actual))
}
for j, e := range test.expected {
actualRule := actual[j].(*rewrite.ComplexRule)
expectedRule := e.(*rewrite.ComplexRule)
if actualRule.Base != expectedRule.Base {
t.Errorf("Test %d, rule %d: Expected Base=%s, got %s",
i, j, expectedRule.Base, actualRule.Base)
}
if actualRule.To != expectedRule.To {
t.Errorf("Test %d, rule %d: Expected To=%s, got %s",
i, j, expectedRule.To, actualRule.To)
}
if fmt.Sprint(actualRule.Exts) != fmt.Sprint(expectedRule.Exts) {
t.Errorf("Test %d, rule %d: Expected Ext=%v, got %v",
i, j, expectedRule.To, actualRule.To)
}
if actualRule.Regexp != nil {
if actualRule.String() != expectedRule.String() {
t.Errorf("Test %d, rule %d: Expected Pattern=%s, got %s",
i, j, expectedRule.String(), actualRule.String())
}
}
if fmt.Sprint(actualRule.Ifs) != fmt.Sprint(expectedRule.Ifs) {
t.Errorf("Test %d, rule %d: Expected Pattern=%s, got %s",
i, j, fmt.Sprint(expectedRule.Ifs), fmt.Sprint(actualRule.Ifs))
}
}
}
}

40
core/setup/roller.go Normal file
View File

@@ -0,0 +1,40 @@
package setup
import (
"strconv"
"github.com/miekg/coredns/middleware"
)
func parseRoller(c *Controller) (*middleware.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 &middleware.LogRoller{
MaxSize: size,
MaxAge: age,
MaxBackups: keep,
LocalTime: true,
}, nil
}

32
core/setup/root.go Normal file
View File

@@ -0,0 +1,32 @@
package setup
import (
"log"
"os"
"github.com/miekg/coredns/middleware"
)
// Root sets up the root file path of the server.
func Root(c *Controller) (middleware.Middleware, error) {
for c.Next() {
if !c.NextArg() {
return nil, c.ArgErr()
}
c.Root = c.Val()
}
// Check if root path exists
_, err := os.Stat(c.Root)
if err != nil {
if os.IsNotExist(err) {
// Allow this, because the folder might appear later.
// But make sure the user knows!
log.Printf("[WARNING] Root path does not exist: %s", c.Root)
} else {
return nil, c.Errf("Unable to access root path '%s': %v", c.Root, err)
}
}
return nil, nil
}

108
core/setup/root_test.go Normal file
View File

@@ -0,0 +1,108 @@
package setup
import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strings"
"testing"
)
func TestRoot(t *testing.T) {
// Predefined error substrings
parseErrContent := "Parse error:"
unableToAccessErrContent := "Unable to access root path"
existingDirPath, err := getTempDirPath()
if err != nil {
t.Fatalf("BeforeTest: Failed to find an existing directory for testing! Error was: %v", err)
}
nonExistingDir := filepath.Join(existingDirPath, "highly_unlikely_to_exist_dir")
existingFile, err := ioutil.TempFile("", "root_test")
if err != nil {
t.Fatalf("BeforeTest: Failed to create temp file for testing! Error was: %v", err)
}
defer func() {
existingFile.Close()
os.Remove(existingFile.Name())
}()
inaccessiblePath := getInaccessiblePath(existingFile.Name())
tests := []struct {
input string
shouldErr bool
expectedRoot string // expected root, set to the controller. Empty for negative cases.
expectedErrContent string // substring from the expected error. Empty for positive cases.
}{
// positive
{
fmt.Sprintf(`root %s`, nonExistingDir), false, nonExistingDir, "",
},
{
fmt.Sprintf(`root %s`, existingDirPath), false, existingDirPath, "",
},
// negative
{
`root `, true, "", parseErrContent,
},
{
fmt.Sprintf(`root %s`, inaccessiblePath), true, "", unableToAccessErrContent,
},
{
fmt.Sprintf(`root {
%s
}`, existingDirPath), true, "", parseErrContent,
},
}
for i, test := range tests {
c := NewTestController(test.input)
mid, err := Root(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)
}
}
// the Root method always returns a nil middleware
if mid != nil {
t.Errorf("Middware, returned from Root() was not nil: %v", mid)
}
// check c.Root only if we are in a positive test.
if !test.shouldErr && test.expectedRoot != c.Root {
t.Errorf("Root not correctly set for input %s. Expected: %s, actual: %s", test.input, test.expectedRoot, c.Root)
}
}
}
// getTempDirPath returnes the path to the system temp directory. If it does not exists - an error is returned.
func getTempDirPath() (string, error) {
tempDir := os.TempDir()
_, err := os.Stat(tempDir)
if err != nil {
return "", err
}
return tempDir, nil
}
func getInaccessiblePath(file string) string {
// null byte in filename is not allowed on Windows AND unix
return filepath.Join("C:", "file\x00name")
}

View File

@@ -0,0 +1,64 @@
package setup
import (
"os"
"os/exec"
"strings"
"github.com/miekg/coredns/middleware"
)
// Startup registers a startup callback to execute during server start.
func Startup(c *Controller) (middleware.Middleware, error) {
return nil, registerCallback(c, &c.FirstStartup)
}
// Shutdown registers a shutdown callback to execute during process exit.
func Shutdown(c *Controller) (middleware.Middleware, error) {
return nil, registerCallback(c, &c.Shutdown)
}
// registerCallback registers a callback function to execute by
// using c to parse the line. It appends the callback function
// to the list of callback functions passed in by reference.
func registerCallback(c *Controller, list *[]func() error) error {
var funcs []func() error
for c.Next() {
args := c.RemainingArgs()
if len(args) == 0 {
return c.ArgErr()
}
nonblock := false
if len(args) > 1 && args[len(args)-1] == "&" {
// Run command in background; non-blocking
nonblock = true
args = args[:len(args)-1]
}
command, args, err := middleware.SplitCommandAndArgs(strings.Join(args, " "))
if err != nil {
return c.Err(err.Error())
}
fn := func() error {
cmd := exec.Command(command, args...)
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if nonblock {
return cmd.Start()
}
return cmd.Run()
}
funcs = append(funcs, fn)
}
return c.OncePerServerBlock(func() error {
*list = append(*list, funcs...)
return nil
})
}

View File

@@ -0,0 +1,59 @@
package setup
import (
"os"
"path/filepath"
"strconv"
"testing"
"time"
)
// The Startup function's tests are symmetrical to Shutdown tests,
// because the Startup and Shutdown functions share virtually the
// same functionality
func TestStartup(t *testing.T) {
tempDirPath, err := getTempDirPath()
if err != nil {
t.Fatalf("BeforeTest: Failed to find an existing directory for testing! Error was: %v", err)
}
testDir := filepath.Join(tempDirPath, "temp_dir_for_testing_startupshutdown")
defer func() {
// clean up after non-blocking startup function quits
time.Sleep(500 * time.Millisecond)
os.RemoveAll(testDir)
}()
osSenitiveTestDir := filepath.FromSlash(testDir)
os.RemoveAll(osSenitiveTestDir) // start with a clean slate
tests := []struct {
input string
shouldExecutionErr bool
shouldRemoveErr bool
}{
// test case #0 tests proper functionality blocking commands
{"startup mkdir " + osSenitiveTestDir, false, false},
// test case #1 tests proper functionality of non-blocking commands
{"startup mkdir " + osSenitiveTestDir + " &", false, true},
// test case #2 tests handling of non-existent commands
{"startup " + strconv.Itoa(int(time.Now().UnixNano())), true, true},
}
for i, test := range tests {
c := NewTestController(test.input)
_, err = Startup(c)
if err != nil {
t.Errorf("Expected no errors, got: %v", err)
}
err = c.FirstStartup[0]()
if err != nil && !test.shouldExecutionErr {
t.Errorf("Test %d recieved an error of:\n%v", i, err)
}
err = os.Remove(osSenitiveTestDir)
if err != nil && !test.shouldRemoveErr {
t.Errorf("Test %d recieved an error of:\n%v", i, err)
}
}
}

View File

@@ -0,0 +1 @@
# Test h1

1
core/setup/testdata/header.html vendored Normal file
View File

@@ -0,0 +1 @@
<h1>Header title</h1>

View File

@@ -0,0 +1,10 @@
<!DOCTYPE html>
<html>
<head>
<title>{{.Doc.title}}</title>
</head>
<body>
{{.Include "header.html"}}
{{.Doc.body}}
</body>
</html>