Skip to content

Commit 710c9b1

Browse files
authored
middleware/root: add it (coredns#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 coredns#307
1 parent baea5ee commit 710c9b1

File tree

10 files changed

+184
-3
lines changed

10 files changed

+184
-3
lines changed

core/coredns.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import (
2121
_ "github.com/miekg/coredns/middleware/pprof"
2222
_ "github.com/miekg/coredns/middleware/proxy"
2323
_ "github.com/miekg/coredns/middleware/rewrite"
24+
_ "github.com/miekg/coredns/middleware/root"
2425
_ "github.com/miekg/coredns/middleware/secondary"
2526
_ "github.com/miekg/coredns/middleware/whoami"
2627
)

core/dnsserver/config.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,10 @@ type Config struct {
1717
// The port to listen on.
1818
Port string
1919

20+
// Root points to a base directory we we find user defined "things".
21+
// First consumer is the file middleware to looks for zone files in this place.
22+
Root string
23+
2024
// Middleware stack.
2125
Middleware []middleware.Middleware
2226

core/dnsserver/directives.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ func RegisterDevDirective(name, before string) {
7373
// (after) them during a request, but they must not
7474
// care what middleware above them are doing.
7575
var directives = []string{
76+
"root",
7677
"bind",
7778
"health",
7879
"pprof",

middleware/file/README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@ zonefile.
1313
file DBFILE [ZONES...]
1414
~~~
1515

16-
* **DBFILE** the database file to read and parse.
16+
* **DBFILE** the database file to read and parse. If the path is relative the path from the *root*
17+
directive will be prepended to it.
1718
* **ZONES** zones it should be authoritative for. If empty, the zones from the configuration block
1819
are used.
1920

middleware/file/setup.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"fmt"
55
"net"
66
"os"
7+
"path"
78

89
"github.com/miekg/coredns/core/dnsserver"
910
"github.com/miekg/coredns/middleware"
@@ -49,6 +50,8 @@ func fileParse(c *caddy.Controller) (Zones, error) {
4950
names := []string{}
5051
origins := []string{}
5152

53+
config := dnsserver.GetConfig(c)
54+
5255
for c.Next() {
5356
if c.Val() == "file" {
5457
// file db.file [zones...]
@@ -64,6 +67,10 @@ func fileParse(c *caddy.Controller) (Zones, error) {
6467
origins = args
6568
}
6669

70+
if !path.IsAbs(fileName) && config.Root != "" {
71+
fileName = path.Join(config.Root, fileName)
72+
}
73+
6774
reader, err := os.Open(fileName)
6875
if err != nil {
6976
// bail out

middleware/file/setup_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ func TestFileParse(t *testing.T) {
5151
}
5252

5353
for i, test := range tests {
54-
c := caddy.NewTestController("file", test.inputFileRules)
54+
c := caddy.NewTestController("dns", test.inputFileRules)
5555
actualZones, err := fileParse(c)
5656

5757
if err == nil && test.shouldErr {

middleware/root/README.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# root
2+
3+
*root* simply specifies the root of where CoreDNS finds (e.g.) zone files.
4+
The default root is the current working directory of CoreDNS.
5+
A relative root path is relative to the current working directory.
6+
7+
## Syntax
8+
9+
~~~ txt
10+
root PATH
11+
~~~
12+
13+
**PATH** is the directory to set as CoreDNS' root.
14+
15+
## Examples
16+
17+
Serve zone data (when the *file* middleware is used) from `/etc/coredns/zones`:
18+
19+
~~~ txt
20+
root /etc/coredns/zones
21+
~~~

middleware/root/root.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package root
2+
3+
import (
4+
"log"
5+
"os"
6+
7+
"github.com/miekg/coredns/core/dnsserver"
8+
9+
"github.com/mholt/caddy"
10+
)
11+
12+
func init() {
13+
caddy.RegisterPlugin("root", caddy.Plugin{
14+
ServerType: "dns",
15+
Action: setup,
16+
})
17+
}
18+
19+
func setup(c *caddy.Controller) error {
20+
config := dnsserver.GetConfig(c)
21+
22+
for c.Next() {
23+
if !c.NextArg() {
24+
return c.ArgErr()
25+
}
26+
config.Root = c.Val()
27+
}
28+
29+
// Check if root path exists
30+
_, err := os.Stat(config.Root)
31+
if err != nil {
32+
if os.IsNotExist(err) {
33+
// Allow this, because the folder might appear later.
34+
// But make sure the user knows!
35+
log.Printf("[WARNING] Root path does not exist: %s", config.Root)
36+
} else {
37+
return c.Errf("Unable to access root path '%s': %v", config.Root, err)
38+
}
39+
}
40+
41+
return nil
42+
}

middleware/root/root_test.go

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
package root
2+
3+
import (
4+
"fmt"
5+
"io/ioutil"
6+
"os"
7+
"path/filepath"
8+
"strings"
9+
"testing"
10+
11+
"github.com/miekg/coredns/core/dnsserver"
12+
13+
"github.com/mholt/caddy"
14+
)
15+
16+
func TestRoot(t *testing.T) {
17+
// Predefined error substrings
18+
parseErrContent := "Parse error:"
19+
unableToAccessErrContent := "Unable to access root path"
20+
21+
existingDirPath, err := getTempDirPath()
22+
if err != nil {
23+
t.Fatalf("BeforeTest: Failed to find an existing directory for testing! Error was: %v", err)
24+
}
25+
26+
nonExistingDir := filepath.Join(existingDirPath, "highly_unlikely_to_exist_dir")
27+
28+
existingFile, err := ioutil.TempFile("", "root_test")
29+
if err != nil {
30+
t.Fatalf("BeforeTest: Failed to create temp file for testing! Error was: %v", err)
31+
}
32+
defer func() {
33+
existingFile.Close()
34+
os.Remove(existingFile.Name())
35+
}()
36+
37+
inaccessiblePath := getInaccessiblePath(existingFile.Name())
38+
39+
tests := []struct {
40+
input string
41+
shouldErr bool
42+
expectedRoot string // expected root, set to the controller. Empty for negative cases.
43+
expectedErrContent string // substring from the expected error. Empty for positive cases.
44+
}{
45+
// positive
46+
{
47+
fmt.Sprintf(`root %s`, nonExistingDir), false, nonExistingDir, "",
48+
},
49+
{
50+
fmt.Sprintf(`root %s`, existingDirPath), false, existingDirPath, "",
51+
},
52+
// negative
53+
{
54+
`root `, true, "", parseErrContent,
55+
},
56+
{
57+
fmt.Sprintf(`root %s`, inaccessiblePath), true, "", unableToAccessErrContent,
58+
},
59+
{
60+
fmt.Sprintf(`root {
61+
%s
62+
}`, existingDirPath), true, "", parseErrContent,
63+
},
64+
}
65+
66+
for i, test := range tests {
67+
c := caddy.NewTestController("dns", test.input)
68+
err := setup(c)
69+
cfg := dnsserver.GetConfig(c)
70+
71+
if test.shouldErr && err == nil {
72+
t.Errorf("Test %d: Expected error but found %s for input %s", i, err, test.input)
73+
}
74+
75+
if err != nil {
76+
if !test.shouldErr {
77+
t.Errorf("Test %d: Expected no error but found one for input %s. Error was: %v", i, test.input, err)
78+
}
79+
80+
if !strings.Contains(err.Error(), test.expectedErrContent) {
81+
t.Errorf("Test %d: Expected error to contain: %v, found error: %v, input: %s", i, test.expectedErrContent, err, test.input)
82+
}
83+
}
84+
85+
// check root only if we are in a positive test.
86+
if !test.shouldErr && test.expectedRoot != cfg.Root {
87+
t.Errorf("Root not correctly set for input %s. Expected: %s, actual: %s", test.input, test.expectedRoot, cfg.Root)
88+
}
89+
}
90+
}
91+
92+
// getTempDirPath returnes the path to the system temp directory. If it does not exists - an error is returned.
93+
func getTempDirPath() (string, error) {
94+
tempDir := os.TempDir()
95+
_, err := os.Stat(tempDir)
96+
if err != nil {
97+
return "", err
98+
}
99+
return tempDir, nil
100+
}
101+
102+
func getInaccessiblePath(file string) string {
103+
return filepath.Join("C:", "file\x00name") // null byte in filename is not allowed on Windows AND unix
104+
}

middleware/secondary/setup_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ func TestSecondaryParse(t *testing.T) {
2929
}
3030

3131
for i, test := range tests {
32-
c := caddy.NewTestController("secondary", test.inputFileRules)
32+
c := caddy.NewTestController("dns", test.inputFileRules)
3333
_, err := secondaryParse(c)
3434

3535
if err == nil && test.shouldErr {

0 commit comments

Comments
 (0)