mirror of
https://github.com/coredns/coredns.git
synced 2025-10-27 16:24:19 -04:00
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:
@@ -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"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
21
middleware/root/README.md
Normal 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
42
middleware/root/root.go
Normal 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
|
||||||
|
}
|
||||||
104
middleware/root/root_test.go
Normal file
104
middleware/root/root_test.go
Normal 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
|
||||||
|
}
|
||||||
@@ -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 {
|
||||||
|
|||||||
Reference in New Issue
Block a user