diff --git a/.gitignore b/.gitignore index 8119cc2c2..a929f5203 100644 --- a/.gitignore +++ b/.gitignore @@ -17,4 +17,5 @@ certs/ spfcache* adzonedump.* zones/ -stack.sh \ No newline at end of file +stack.sh +.idea/ \ No newline at end of file diff --git a/commands/printIR.go b/commands/printIR.go index 021be7700..1da8889ce 100644 --- a/commands/printIR.go +++ b/commands/printIR.go @@ -3,7 +3,6 @@ package commands import ( "encoding/json" "fmt" - "io/ioutil" "os" "github.com/StackExchange/dnscontrol/models" @@ -101,11 +100,8 @@ func ExecuteDSL(args ExecuteDSLArgs) (*models.DNSConfig, error) { if args.JSFile == "" { return nil, errors.Errorf("No config specified") } - text, err := ioutil.ReadFile(args.JSFile) - if err != nil { - return nil, errors.Errorf("Reading js file %s: %s", args.JSFile, err) - } - dnsConfig, err := js.ExecuteJavascript(string(text), args.DevMode) + + dnsConfig, err := js.ExecuteJavascript(args.JSFile, args.DevMode) if err != nil { return nil, errors.Errorf("Executing javascript in %s: %s", args.JSFile, err) } diff --git a/docs/_functions/global/require.md b/docs/_functions/global/require.md new file mode 100644 index 000000000..9790e9215 --- /dev/null +++ b/docs/_functions/global/require.md @@ -0,0 +1,52 @@ +--- +name: require +parameters: + - path +--- + +`require(...)` behaves similarly to its equivalent in node.js. You can use it +to split your configuration across multiple files. If the path starts with a +`.`, it is calculated relative to the current file. For example: + +{% include startExample.html %} +{% highlight js %} + +// dnsconfig.js +require('kubernetes/clusters.js'); + +D("mydomain.net", REG, PROVIDER, + IncludeKubernetes() +); + +{%endhighlight%} + +{% highlight js %} + +// kubernetes/clusters.js +require('./clusters/prod.js'); +require('./clusters/dev.js'); + +function IncludeKubernetes() { + return [includeK8Sprod(), includeK8Sdev()]; +} + +{%endhighlight%} + +{% highlight js %} + +// kubernetes/clusters/prod.js +function includeK8Sprod() { + return [ /* ... */ ]; +} + +{%endhighlight%} + +{% highlight js %} + +// kubernetes/clusters/dev.js +function includeK8Sdev() { + return [ /* ... */ ]; +} + +{%endhighlight%} +{% include endExample.html %} diff --git a/pkg/js/js.go b/pkg/js/js.go index f8f73ce0e..77f5a9bd3 100644 --- a/pkg/js/js.go +++ b/pkg/js/js.go @@ -3,19 +3,35 @@ package js import ( "encoding/json" "io/ioutil" + "path/filepath" + "strings" "github.com/StackExchange/dnscontrol/models" "github.com/StackExchange/dnscontrol/pkg/printer" "github.com/StackExchange/dnscontrol/pkg/transform" - - "github.com/robertkrimen/otto" - // load underscore js into vm by default - + "github.com/pkg/errors" + "github.com/robertkrimen/otto" // load underscore js into vm by default _ "github.com/robertkrimen/otto/underscore" // required by otto ) +// currentDirectory is the current directory as used by require(). +// This is used to emulate nodejs-style require() directory handling. +// If require("a/b/c.js") is called, any require() statement in c.js +// needs to be accessed relative to "a/b". Therefore we +// track the currentDirectory (which is the current directory as +// far as require() is concerned, not the actual os.Getwd(). +var currentDirectory string + // ExecuteJavascript accepts a javascript string and runs it, returning the resulting dnsConfig. -func ExecuteJavascript(script string, devMode bool) (*models.DNSConfig, error) { +func ExecuteJavascript(file string, devMode bool) (*models.DNSConfig, error) { + script, err := ioutil.ReadFile(file) + if err != nil { + return nil, errors.Errorf("Reading js file %s: %s", file, err) + } + + // Record the directory path leading up to this file. + currentDirectory = filepath.Clean(filepath.Dir(file)) + vm := otto.New() vm.Set("require", require) @@ -57,16 +73,36 @@ func require(call otto.FunctionCall) otto.Value { if len(call.ArgumentList) != 1 { throw(call.Otto, "require takes exactly one argument") } - file := call.Argument(0).String() - printer.Debugf("requiring: %s\n", file) - data, err := ioutil.ReadFile(file) + file := call.Argument(0).String() // The filename as given by the user + + // relFile is the file we're actually going to pass to ReadFile(). + // It defaults to the user-provided name unless it is relative. + relFile := file + cleanFile := filepath.Clean(filepath.Join(currentDirectory, file)) + if strings.HasPrefix(file, ".") { + relFile = cleanFile + } + + // Record the old currentDirectory so that we can return there. + currentDirectoryOld := currentDirectory + // Record the directory path leading up to the file we're about to require. + currentDirectory = filepath.Clean(filepath.Dir(cleanFile)) + + printer.Debugf("requiring: %s (%s)\n", file, relFile) + data, err := ioutil.ReadFile(relFile) + if err != nil { throw(call.Otto, err.Error()) } + _, err = call.Otto.Run(string(data)) if err != nil { throw(call.Otto, err.Error()) } + + // Pop back to the old directory. + currentDirectory = currentDirectoryOld + return otto.TrueValue() } diff --git a/pkg/js/js_test.go b/pkg/js/js_test.go index b9730783c..1962f889f 100644 --- a/pkg/js/js_test.go +++ b/pkg/js/js_test.go @@ -34,11 +34,7 @@ func TestParsedFiles(t *testing.T) { m := minify.New() m.AddFunc("json", minjson.Minify) t.Run(f.Name(), func(t *testing.T) { - content, err := ioutil.ReadFile(filepath.Join(testDir, f.Name())) - if err != nil { - t.Fatal(err) - } - conf, err := ExecuteJavascript(string(content), true) + conf, err := ExecuteJavascript(string(filepath.Join(testDir, f.Name())), true) if err != nil { t.Fatal(err) } diff --git a/pkg/js/parse_tests/008-import.js b/pkg/js/parse_tests/008-import.js index d1c689a5f..5ffd406ec 100644 --- a/pkg/js/parse_tests/008-import.js +++ b/pkg/js/parse_tests/008-import.js @@ -1,2 +1,2 @@ -require("pkg/js/parse_tests/import.js") +require("./import.js") diff --git a/pkg/js/parse_tests/020-complexRequire.js b/pkg/js/parse_tests/020-complexRequire.js new file mode 100644 index 000000000..6c8d1148b --- /dev/null +++ b/pkg/js/parse_tests/020-complexRequire.js @@ -0,0 +1 @@ +require('./complexImports/base.js'); \ No newline at end of file diff --git a/pkg/js/parse_tests/020-complexRequire.json b/pkg/js/parse_tests/020-complexRequire.json new file mode 100644 index 000000000..50a2a8ef7 --- /dev/null +++ b/pkg/js/parse_tests/020-complexRequire.json @@ -0,0 +1,38 @@ +{ + "registrars": [], + "dns_providers": [], + "domains": [ + { + "name": "foo.com", + "registrar": "none", + "dnsProviders": {}, + "records": [ + { + "type": "A", + "name": "@", + "target": "1.2.3.4" + }, + { + "type": "CNAME", + "name": "A", + "target": "foo.com." + }, + { + "type": "CNAME", + "name": "C", + "target": "foo.com." + }, + { + "type": "CNAME", + "name": "D", + "target": "foo.com." + }, + { + "type": "CNAME", + "name": "B", + "target": "foo.com." + } + ] + } + ] +} \ No newline at end of file diff --git a/pkg/js/parse_tests/complexImports/a/a.js b/pkg/js/parse_tests/complexImports/a/a.js new file mode 100644 index 000000000..e0ab3a0c5 --- /dev/null +++ b/pkg/js/parse_tests/complexImports/a/a.js @@ -0,0 +1,3 @@ +function a() { + return CNAME("A", "foo.com.") +} \ No newline at end of file diff --git a/pkg/js/parse_tests/complexImports/a/c/c.js b/pkg/js/parse_tests/complexImports/a/c/c.js new file mode 100644 index 000000000..1cb1fad86 --- /dev/null +++ b/pkg/js/parse_tests/complexImports/a/c/c.js @@ -0,0 +1,8 @@ +require('../a.js'); + +function c() { + return [ + a(), + CNAME("C", "foo.com.") + ] +} \ No newline at end of file diff --git a/pkg/js/parse_tests/complexImports/b/b.js b/pkg/js/parse_tests/complexImports/b/b.js new file mode 100644 index 000000000..aa2491a70 --- /dev/null +++ b/pkg/js/parse_tests/complexImports/b/b.js @@ -0,0 +1,8 @@ +require('pkg/js/parse_tests/complexImports/b/d/d.js'); + +function b() { + return [ + d(), + CNAME("B", "foo.com.") + ]; +} \ No newline at end of file diff --git a/pkg/js/parse_tests/complexImports/b/d/d.js b/pkg/js/parse_tests/complexImports/b/d/d.js new file mode 100644 index 000000000..2a6af0c2e --- /dev/null +++ b/pkg/js/parse_tests/complexImports/b/d/d.js @@ -0,0 +1,3 @@ +function d() { + return CNAME("D", "foo.com.") +} \ No newline at end of file diff --git a/pkg/js/parse_tests/complexImports/base.js b/pkg/js/parse_tests/complexImports/base.js new file mode 100644 index 000000000..7fe5770c4 --- /dev/null +++ b/pkg/js/parse_tests/complexImports/base.js @@ -0,0 +1,8 @@ +require('./a/c/c.js'); +require('./b/b.js'); + +D("foo.com","none", + A("@","1.2.3.4"), + c(), + b() +); \ No newline at end of file