mirror of
https://github.com/StackExchange/dnscontrol.git
synced 2024-05-11 05:55:12 +00:00
require() should handle paths as relative (like nodeJS) (#443)
* Integrate @nlowe's documentation and tests * Use filepath.Clean() instead of filepath.Abs() * Use a stackless implementation
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@ -18,3 +18,4 @@ spfcache*
|
|||||||
adzonedump.*
|
adzonedump.*
|
||||||
zones/
|
zones/
|
||||||
stack.sh
|
stack.sh
|
||||||
|
.idea/
|
@ -3,7 +3,6 @@ package commands
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/StackExchange/dnscontrol/models"
|
"github.com/StackExchange/dnscontrol/models"
|
||||||
@ -101,11 +100,8 @@ func ExecuteDSL(args ExecuteDSLArgs) (*models.DNSConfig, error) {
|
|||||||
if args.JSFile == "" {
|
if args.JSFile == "" {
|
||||||
return nil, errors.Errorf("No config specified")
|
return nil, errors.Errorf("No config specified")
|
||||||
}
|
}
|
||||||
text, err := ioutil.ReadFile(args.JSFile)
|
|
||||||
if err != nil {
|
dnsConfig, err := js.ExecuteJavascript(args.JSFile, args.DevMode)
|
||||||
return nil, errors.Errorf("Reading js file %s: %s", args.JSFile, err)
|
|
||||||
}
|
|
||||||
dnsConfig, err := js.ExecuteJavascript(string(text), args.DevMode)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Errorf("Executing javascript in %s: %s", args.JSFile, err)
|
return nil, errors.Errorf("Executing javascript in %s: %s", args.JSFile, err)
|
||||||
}
|
}
|
||||||
|
52
docs/_functions/global/require.md
Normal file
52
docs/_functions/global/require.md
Normal file
@ -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 %}
|
52
pkg/js/js.go
52
pkg/js/js.go
@ -3,19 +3,35 @@ package js
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/StackExchange/dnscontrol/models"
|
"github.com/StackExchange/dnscontrol/models"
|
||||||
"github.com/StackExchange/dnscontrol/pkg/printer"
|
"github.com/StackExchange/dnscontrol/pkg/printer"
|
||||||
"github.com/StackExchange/dnscontrol/pkg/transform"
|
"github.com/StackExchange/dnscontrol/pkg/transform"
|
||||||
|
"github.com/pkg/errors"
|
||||||
"github.com/robertkrimen/otto"
|
"github.com/robertkrimen/otto" // load underscore js into vm by default
|
||||||
// load underscore js into vm by default
|
|
||||||
|
|
||||||
_ "github.com/robertkrimen/otto/underscore" // required by otto
|
_ "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.
|
// 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 := otto.New()
|
||||||
|
|
||||||
vm.Set("require", require)
|
vm.Set("require", require)
|
||||||
@ -57,16 +73,36 @@ func require(call otto.FunctionCall) otto.Value {
|
|||||||
if len(call.ArgumentList) != 1 {
|
if len(call.ArgumentList) != 1 {
|
||||||
throw(call.Otto, "require takes exactly one argument")
|
throw(call.Otto, "require takes exactly one argument")
|
||||||
}
|
}
|
||||||
file := call.Argument(0).String()
|
file := call.Argument(0).String() // The filename as given by the user
|
||||||
printer.Debugf("requiring: %s\n", file)
|
|
||||||
data, err := ioutil.ReadFile(file)
|
// 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 {
|
if err != nil {
|
||||||
throw(call.Otto, err.Error())
|
throw(call.Otto, err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = call.Otto.Run(string(data))
|
_, err = call.Otto.Run(string(data))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
throw(call.Otto, err.Error())
|
throw(call.Otto, err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Pop back to the old directory.
|
||||||
|
currentDirectory = currentDirectoryOld
|
||||||
|
|
||||||
return otto.TrueValue()
|
return otto.TrueValue()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -34,11 +34,7 @@ func TestParsedFiles(t *testing.T) {
|
|||||||
m := minify.New()
|
m := minify.New()
|
||||||
m.AddFunc("json", minjson.Minify)
|
m.AddFunc("json", minjson.Minify)
|
||||||
t.Run(f.Name(), func(t *testing.T) {
|
t.Run(f.Name(), func(t *testing.T) {
|
||||||
content, err := ioutil.ReadFile(filepath.Join(testDir, f.Name()))
|
conf, err := ExecuteJavascript(string(filepath.Join(testDir, f.Name())), true)
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
conf, err := ExecuteJavascript(string(content), true)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -1,2 +1,2 @@
|
|||||||
|
|
||||||
require("pkg/js/parse_tests/import.js")
|
require("./import.js")
|
||||||
|
1
pkg/js/parse_tests/020-complexRequire.js
Normal file
1
pkg/js/parse_tests/020-complexRequire.js
Normal file
@ -0,0 +1 @@
|
|||||||
|
require('./complexImports/base.js');
|
38
pkg/js/parse_tests/020-complexRequire.json
Normal file
38
pkg/js/parse_tests/020-complexRequire.json
Normal file
@ -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."
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
3
pkg/js/parse_tests/complexImports/a/a.js
Normal file
3
pkg/js/parse_tests/complexImports/a/a.js
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
function a() {
|
||||||
|
return CNAME("A", "foo.com.")
|
||||||
|
}
|
8
pkg/js/parse_tests/complexImports/a/c/c.js
Normal file
8
pkg/js/parse_tests/complexImports/a/c/c.js
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
require('../a.js');
|
||||||
|
|
||||||
|
function c() {
|
||||||
|
return [
|
||||||
|
a(),
|
||||||
|
CNAME("C", "foo.com.")
|
||||||
|
]
|
||||||
|
}
|
8
pkg/js/parse_tests/complexImports/b/b.js
Normal file
8
pkg/js/parse_tests/complexImports/b/b.js
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
require('pkg/js/parse_tests/complexImports/b/d/d.js');
|
||||||
|
|
||||||
|
function b() {
|
||||||
|
return [
|
||||||
|
d(),
|
||||||
|
CNAME("B", "foo.com.")
|
||||||
|
];
|
||||||
|
}
|
3
pkg/js/parse_tests/complexImports/b/d/d.js
Normal file
3
pkg/js/parse_tests/complexImports/b/d/d.js
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
function d() {
|
||||||
|
return CNAME("D", "foo.com.")
|
||||||
|
}
|
8
pkg/js/parse_tests/complexImports/base.js
Normal file
8
pkg/js/parse_tests/complexImports/base.js
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
require('./a/c/c.js');
|
||||||
|
require('./b/b.js');
|
||||||
|
|
||||||
|
D("foo.com","none",
|
||||||
|
A("@","1.2.3.4"),
|
||||||
|
c(),
|
||||||
|
b()
|
||||||
|
);
|
Reference in New Issue
Block a user