middleware/root: add it (#330)

This PR adds the *root* middleware that specifies a path where
all zone file (the *file* middleware is the only consumer now) can
be found. It works the same as in Caddy.

Documentation can be found in the README.md of the middleware.

Fixes #307
This commit is contained in:
Miek Gieben
2016-10-11 20:42:28 +01:00
committed by GitHub
parent baea5eef2f
commit 710c9b111f
10 changed files with 184 additions and 3 deletions

View File

@@ -21,6 +21,7 @@ import (
_ "github.com/miekg/coredns/middleware/pprof" _ "github.com/miekg/coredns/middleware/pprof"
_ "github.com/miekg/coredns/middleware/proxy" _ "github.com/miekg/coredns/middleware/proxy"
_ "github.com/miekg/coredns/middleware/rewrite" _ "github.com/miekg/coredns/middleware/rewrite"
_ "github.com/miekg/coredns/middleware/root"
_ "github.com/miekg/coredns/middleware/secondary" _ "github.com/miekg/coredns/middleware/secondary"
_ "github.com/miekg/coredns/middleware/whoami" _ "github.com/miekg/coredns/middleware/whoami"
) )

View File

@@ -17,6 +17,10 @@ type Config struct {
// The port to listen on. // The port to listen on.
Port string Port string
// Root points to a base directory we we find user defined "things".
// First consumer is the file middleware to looks for zone files in this place.
Root string
// Middleware stack. // Middleware stack.
Middleware []middleware.Middleware Middleware []middleware.Middleware

View File

@@ -73,6 +73,7 @@ func RegisterDevDirective(name, before string) {
// (after) them during a request, but they must not // (after) them during a request, but they must not
// care what middleware above them are doing. // care what middleware above them are doing.
var directives = []string{ var directives = []string{
"root",
"bind", "bind",
"health", "health",
"pprof", "pprof",

View File

@@ -13,7 +13,8 @@ zonefile.
file DBFILE [ZONES...] file DBFILE [ZONES...]
~~~ ~~~
* **DBFILE** the database file to read and parse. * **DBFILE** the database file to read and parse. If the path is relative the path from the *root*
directive will be prepended to it.
* **ZONES** zones it should be authoritative for. If empty, the zones from the configuration block * **ZONES** zones it should be authoritative for. If empty, the zones from the configuration block
are used. are used.

View File

@@ -4,6 +4,7 @@ import (
"fmt" "fmt"
"net" "net"
"os" "os"
"path"
"github.com/miekg/coredns/core/dnsserver" "github.com/miekg/coredns/core/dnsserver"
"github.com/miekg/coredns/middleware" "github.com/miekg/coredns/middleware"
@@ -49,6 +50,8 @@ func fileParse(c *caddy.Controller) (Zones, error) {
names := []string{} names := []string{}
origins := []string{} origins := []string{}
config := dnsserver.GetConfig(c)
for c.Next() { for c.Next() {
if c.Val() == "file" { if c.Val() == "file" {
// file db.file [zones...] // file db.file [zones...]
@@ -64,6 +67,10 @@ func fileParse(c *caddy.Controller) (Zones, error) {
origins = args origins = args
} }
if !path.IsAbs(fileName) && config.Root != "" {
fileName = path.Join(config.Root, fileName)
}
reader, err := os.Open(fileName) reader, err := os.Open(fileName)
if err != nil { if err != nil {
// bail out // bail out

View File

@@ -51,7 +51,7 @@ func TestFileParse(t *testing.T) {
} }
for i, test := range tests { for i, test := range tests {
c := caddy.NewTestController("file", test.inputFileRules) c := caddy.NewTestController("dns", test.inputFileRules)
actualZones, err := fileParse(c) actualZones, err := fileParse(c)
if err == nil && test.shouldErr { if err == nil && test.shouldErr {

21
middleware/root/README.md Normal file
View File

@@ -0,0 +1,21 @@
# root
*root* simply specifies the root of where CoreDNS finds (e.g.) zone files.
The default root is the current working directory of CoreDNS.
A relative root path is relative to the current working directory.
## Syntax
~~~ txt
root PATH
~~~
**PATH** is the directory to set as CoreDNS' root.
## Examples
Serve zone data (when the *file* middleware is used) from `/etc/coredns/zones`:
~~~ txt
root /etc/coredns/zones
~~~

42
middleware/root/root.go Normal file
View File

@@ -0,0 +1,42 @@
package root
import (
"log"
"os"
"github.com/miekg/coredns/core/dnsserver"
"github.com/mholt/caddy"
)
func init() {
caddy.RegisterPlugin("root", caddy.Plugin{
ServerType: "dns",
Action: setup,
})
}
func setup(c *caddy.Controller) error {
config := dnsserver.GetConfig(c)
for c.Next() {
if !c.NextArg() {
return c.ArgErr()
}
config.Root = c.Val()
}
// Check if root path exists
_, err := os.Stat(config.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", config.Root)
} else {
return c.Errf("Unable to access root path '%s': %v", config.Root, err)
}
}
return nil
}

View File

@@ -0,0 +1,104 @@
package root
import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strings"
"testing"
"github.com/miekg/coredns/core/dnsserver"
"github.com/mholt/caddy"
)
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 := caddy.NewTestController("dns", test.input)
err := setup(c)
cfg := dnsserver.GetConfig(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)
}
}
// check root only if we are in a positive test.
if !test.shouldErr && test.expectedRoot != cfg.Root {
t.Errorf("Root not correctly set for input %s. Expected: %s, actual: %s", test.input, test.expectedRoot, cfg.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 {
return filepath.Join("C:", "file\x00name") // null byte in filename is not allowed on Windows AND unix
}

View File

@@ -29,7 +29,7 @@ func TestSecondaryParse(t *testing.T) {
} }
for i, test := range tests { for i, test := range tests {
c := caddy.NewTestController("secondary", test.inputFileRules) c := caddy.NewTestController("dns", test.inputFileRules)
_, err := secondaryParse(c) _, err := secondaryParse(c)
if err == nil && test.shouldErr { if err == nil && test.shouldErr {