mirror of
https://github.com/StackExchange/dnscontrol.git
synced 2024-05-11 05:55:12 +00:00
Nameserver overhaul (#17)
* go changes to support nameservers_from * clear nameservers before giving to dsp. * work * work * nameserver updates. * remove unused * name.com stinks at NS records. * whitespace * removing web(belongs in own repo). First sketch of DSP vs NAMESERVER_FROM * add DEFAULTS to replace defaultDsps. * initial gcloud provider. Simple records work. * namedotcom can do subdomain ns records now. * fix for mx and txt * kill dsp acronym
This commit is contained in:
4
.gitignore
vendored
4
.gitignore
vendored
@ -3,3 +3,7 @@ dnscontrol-Darwin
|
||||
dnscontrol-Linux
|
||||
dnscontrol.exe
|
||||
dnscontrol
|
||||
dnsconfig.js
|
||||
creds.json
|
||||
integrationTest
|
||||
ExternalDNS
|
||||
|
1
js/error_tests/01-oldDSP.js
Normal file
1
js/error_tests/01-oldDSP.js
Normal file
@ -0,0 +1 @@
|
||||
D("foo.com","reg","dsp")
|
@ -2,19 +2,19 @@
|
||||
|
||||
var conf = {
|
||||
registrars: [],
|
||||
dns_service_providers: [],
|
||||
dns_providers: [],
|
||||
domains: []
|
||||
};
|
||||
|
||||
var defaultDsps = [];
|
||||
var defaultArgs = [];
|
||||
|
||||
function initialize(){
|
||||
conf = {
|
||||
registrars: [],
|
||||
dns_service_providers: [],
|
||||
dns_providers: [],
|
||||
domains: []
|
||||
};
|
||||
defaultDsps = [];
|
||||
defaultArgs = [];
|
||||
}
|
||||
|
||||
function NewRegistrar(name,type,meta) {
|
||||
@ -26,17 +26,17 @@ function NewRegistrar(name,type,meta) {
|
||||
return name;
|
||||
}
|
||||
|
||||
function NewDSP(name, type, meta) {
|
||||
function NewDnsProvider(name, type, meta) {
|
||||
if ((typeof meta === 'object') && ('ip_conversions' in meta)) {
|
||||
meta.ip_conversions = format_tt(meta.ip_conversions)
|
||||
}
|
||||
var dsp = {name: name, type: type, meta: meta};
|
||||
conf.dns_service_providers.push(dsp);
|
||||
conf.dns_providers.push(dsp);
|
||||
return name;
|
||||
}
|
||||
|
||||
function newDomain(name,registrar) {
|
||||
return {name: name, registrar: registrar, meta:{}, records:[], dsps: [], defaultTTL: 0, nameservers:[]};
|
||||
return {name: name, registrar: registrar, meta:{}, records:[], dnsProviders: {}, defaultTTL: 0, nameservers:[]};
|
||||
}
|
||||
|
||||
function processDargs(m, domain) {
|
||||
@ -44,7 +44,6 @@ function processDargs(m, domain) {
|
||||
// function: call it with domain
|
||||
// array: process recursively
|
||||
// object: merge it into metadata
|
||||
// string: assume it is a dsp
|
||||
if (_.isFunction(m)) {
|
||||
m(domain);
|
||||
} else if (_.isArray(m)) {
|
||||
@ -53,10 +52,8 @@ function processDargs(m, domain) {
|
||||
}
|
||||
} else if (_.isObject(m)) {
|
||||
_.extend(domain.meta,m);
|
||||
} else if (_.isString(m)) {
|
||||
domain.dsps.push(m);
|
||||
} else {
|
||||
console.log("WARNING: domain modifier type unsupported: ", typeof m, " Domain: ", domain)
|
||||
throw "WARNING: domain modifier type unsupported: "+ typeof m + " Domain: "+ domain.name;
|
||||
}
|
||||
}
|
||||
|
||||
@ -67,11 +64,21 @@ function D(name,registrar) {
|
||||
var m = arguments[i];
|
||||
processDargs(m, domain)
|
||||
}
|
||||
var toAdd = _(defaultDsps).difference(domain.dsps);
|
||||
_(toAdd).each(function(x) { domain.dsps.push(x)});
|
||||
for (var i = 0; i< defaultArgs.length; i++){
|
||||
processDargs(defaultArgs[i],domain)
|
||||
}
|
||||
conf.domains.push(domain)
|
||||
}
|
||||
|
||||
// DEFAULTS provides a set of default arguments to apply to all future domains.
|
||||
// Each call to DEFAULTS will clear any previous values set.
|
||||
function DEFAULTS(){
|
||||
defaultArgs = [];
|
||||
for (var i = 0; i<arguments.length; i++) {
|
||||
defaultArgs.push(arguments[i]);
|
||||
}
|
||||
}
|
||||
|
||||
// TTL(v): Set the TTL for a DNS record.
|
||||
function TTL(v) {
|
||||
return function(r) {
|
||||
@ -86,6 +93,20 @@ function DefaultTTL(v) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// DnsProvider("providerName", 0)
|
||||
// nsCount of 0 means don't use or register any nameservers.
|
||||
// nsCount not provider means use all.
|
||||
function DnsProvider(name, nsCount){
|
||||
if(typeof nsCount === 'undefined'){
|
||||
nsCount = -1;
|
||||
}
|
||||
return function(d) {
|
||||
d.dnsProviders[name] = nsCount;
|
||||
}
|
||||
}
|
||||
|
||||
// A(name,ip, recordModifiers...)
|
||||
function A(name, ip) {
|
||||
var mods = getModifiers(arguments,2)
|
||||
|
@ -10,10 +10,16 @@ import (
|
||||
"github.com/StackExchange/dnscontrol/models"
|
||||
)
|
||||
|
||||
const testDir = "js/parse_tests"
|
||||
const (
|
||||
testDir = "js/parse_tests"
|
||||
errorDir = "js/error_tests"
|
||||
)
|
||||
|
||||
func init() {
|
||||
os.Chdir("..") // go up a directory so we helpers.js is in a consistent place.
|
||||
}
|
||||
|
||||
func TestParsedFiles(t *testing.T) {
|
||||
os.Chdir("..") // go up a directory so we helpers.js is in a consistent place.
|
||||
files, err := ioutil.ReadDir(testDir)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@ -53,6 +59,27 @@ func TestParsedFiles(t *testing.T) {
|
||||
t.Error("Expected and actual json don't match")
|
||||
t.Log("Expected:", string(expectedJson))
|
||||
t.Log("Actual:", string(actualJson))
|
||||
t.FailNow()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestErrors(t *testing.T) {
|
||||
files, err := ioutil.ReadDir(errorDir)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for _, f := range files {
|
||||
if filepath.Ext(f.Name()) != ".js" {
|
||||
continue
|
||||
}
|
||||
t.Log(f.Name(), "------")
|
||||
content, err := ioutil.ReadFile(filepath.Join(errorDir, f.Name()))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err = ExecuteJavascript(string(content), true); err == nil {
|
||||
t.Fatal("Expected error but found none")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
|
||||
var REG = NewRegistrar("Third-Party","NONE");
|
||||
var CF = NewDSP("Cloudflare", "CLOUDFLAREAPI")
|
||||
D("foo.com",REG,CF,
|
||||
var CF = NewDnsProvider("Cloudflare", "CLOUDFLAREAPI")
|
||||
D("foo.com",REG,DnsProvider(CF),
|
||||
A("@","1.2.3.4")
|
||||
);
|
@ -5,7 +5,7 @@
|
||||
"type": "NONE"
|
||||
}
|
||||
],
|
||||
"dns_service_providers": [
|
||||
"dns_providers": [
|
||||
{
|
||||
"name": "Cloudflare",
|
||||
"type": "CLOUDFLAREAPI"
|
||||
@ -15,9 +15,9 @@
|
||||
{
|
||||
"name": "foo.com",
|
||||
"registrar": "Third-Party",
|
||||
"dsps": [
|
||||
"Cloudflare"
|
||||
],
|
||||
"dnsProviders": {
|
||||
"Cloudflare":-1
|
||||
},
|
||||
"records": [
|
||||
{
|
||||
"type": "A",
|
||||
|
@ -1,5 +1,5 @@
|
||||
var REG = NewRegistrar("Third-Party","NONE");
|
||||
var CF = NewDSP("Cloudflare", "CLOUDFLAREAPI")
|
||||
D("foo.com",REG,CF,
|
||||
var CF = NewDnsProvider("Cloudflare", "CLOUDFLAREAPI")
|
||||
D("foo.com",REG,DnsProvider(CF),
|
||||
A("@","1.2.3.4",TTL(42))
|
||||
);
|
@ -5,7 +5,7 @@
|
||||
"type": "NONE"
|
||||
}
|
||||
],
|
||||
"dns_service_providers": [
|
||||
"dns_providers": [
|
||||
{
|
||||
"name": "Cloudflare",
|
||||
"type": "CLOUDFLAREAPI"
|
||||
@ -15,9 +15,9 @@
|
||||
{
|
||||
"name": "foo.com",
|
||||
"registrar": "Third-Party",
|
||||
"dsps": [
|
||||
"Cloudflare"
|
||||
],
|
||||
"dnsProviders": {
|
||||
"Cloudflare":-1
|
||||
},
|
||||
"records": [
|
||||
{
|
||||
"type": "A",
|
||||
|
@ -5,12 +5,12 @@
|
||||
"type": "CLOUDFLAREAPI"
|
||||
}
|
||||
],
|
||||
"dns_service_providers": [],
|
||||
"dns_providers": [],
|
||||
"domains": [
|
||||
{
|
||||
"name": "foo.com",
|
||||
"registrar": "Cloudflare",
|
||||
"dsps": [],
|
||||
"dnsProviders": {},
|
||||
"records": [
|
||||
{
|
||||
"type": "A",
|
||||
|
@ -1,9 +1,9 @@
|
||||
var REG = NewRegistrar("Third-Party","NONE");
|
||||
var CF = NewDSP("Cloudflare", "CLOUDFLAREAPI")
|
||||
var CF = NewDnsProvider("Cloudflare", "CLOUDFLAREAPI")
|
||||
|
||||
var BASE = IP("1.2.3.4")
|
||||
|
||||
D("foo.com",REG,CF,
|
||||
D("foo.com",REG,DnsProvider(CF,0),
|
||||
A("@",BASE),
|
||||
A("p1",BASE+1),
|
||||
A("p255", BASE+255)
|
||||
|
@ -5,7 +5,7 @@
|
||||
"type": "NONE"
|
||||
}
|
||||
],
|
||||
"dns_service_providers": [
|
||||
"dns_providers": [
|
||||
{
|
||||
"name": "Cloudflare",
|
||||
"type": "CLOUDFLAREAPI"
|
||||
@ -15,9 +15,9 @@
|
||||
{
|
||||
"name": "foo.com",
|
||||
"registrar": "Third-Party",
|
||||
"dsps": [
|
||||
"Cloudflare"
|
||||
],
|
||||
"dnsProviders": {
|
||||
"Cloudflare":0
|
||||
},
|
||||
"records": [
|
||||
{ "type": "A","name": "@","target": "1.2.3.4"},
|
||||
{ "type": "A","name": "p1","target": "1.2.3.5"},
|
||||
|
@ -1,7 +1,7 @@
|
||||
|
||||
var REG = NewRegistrar("Third-Party","NONE");
|
||||
var CF = NewDSP("Cloudflare", "CLOUDFLAREAPI")
|
||||
D("foo.com",REG,CF,
|
||||
var CF = NewDnsProvider("Cloudflare", "CLOUDFLAREAPI")
|
||||
D("foo.com",REG,DnsProvider(CF,2),
|
||||
A("@","1.2.3.4")
|
||||
);
|
||||
D("foo.com",REG);
|
@ -4,7 +4,7 @@
|
||||
"type": "NONE"
|
||||
}
|
||||
],
|
||||
"dns_service_providers": [
|
||||
"dns_providers": [
|
||||
{
|
||||
"name": "Cloudflare",
|
||||
"type": "CLOUDFLAREAPI"
|
||||
@ -14,9 +14,9 @@
|
||||
{
|
||||
"name": "foo.com",
|
||||
"registrar": "Third-Party",
|
||||
"dsps": [
|
||||
"Cloudflare"
|
||||
],
|
||||
"dnsProviders": {
|
||||
"Cloudflare":2
|
||||
},
|
||||
"records": [
|
||||
{
|
||||
"type": "A",
|
||||
@ -28,7 +28,7 @@
|
||||
{
|
||||
"name": "foo.com",
|
||||
"registrar": "Third-Party",
|
||||
"dsps": [],
|
||||
"dnsProviders": {},
|
||||
"records": []
|
||||
}
|
||||
]
|
||||
|
@ -1,5 +1,5 @@
|
||||
var REG = NewRegistrar("Third-Party","NONE");
|
||||
var CF = NewDSP("Cloudflare", "CLOUDFLAREAPI")
|
||||
var CF = NewDnsProvider("Cloudflare", "CLOUDFLAREAPI")
|
||||
|
||||
var TRANSFORM_INT = [
|
||||
{low: "0.0.0.0", high: "1.1.1.1", newBase: "2.2.2.2" },
|
||||
@ -7,6 +7,6 @@ var TRANSFORM_INT = [
|
||||
{low: "1.1.1.1", high: IP("2.2.2.2"), newIP: ["3.3.3.3","4.4.4.4",IP("5.5.5.5")]}
|
||||
]
|
||||
|
||||
D("foo.com",REG,CF,
|
||||
D("foo.com",REG,DnsProvider(CF),
|
||||
A("@","1.2.3.4",{transform: TRANSFORM_INT})
|
||||
);
|
@ -5,7 +5,7 @@
|
||||
"type": "NONE"
|
||||
}
|
||||
],
|
||||
"dns_service_providers": [
|
||||
"dns_providers": [
|
||||
{
|
||||
"name": "Cloudflare",
|
||||
"type": "CLOUDFLAREAPI"
|
||||
@ -15,9 +15,9 @@
|
||||
{
|
||||
"name": "foo.com",
|
||||
"registrar": "Third-Party",
|
||||
"dsps": [
|
||||
"Cloudflare"
|
||||
],
|
||||
"dnsProviders": {
|
||||
"Cloudflare":-1
|
||||
},
|
||||
"records": [
|
||||
{
|
||||
"type": "A",
|
||||
|
@ -1,11 +1,11 @@
|
||||
{
|
||||
"registrars": [],
|
||||
"dns_service_providers": [],
|
||||
"dns_providers": [],
|
||||
"domains": [
|
||||
{
|
||||
"name": "foo.com",
|
||||
"registrar": "reg",
|
||||
"dsps": [],
|
||||
"dnsProviders": {},
|
||||
"records": [
|
||||
{
|
||||
"type": "IMPORT_TRANSFORM",
|
||||
|
74
js/static.go
74
js/static.go
@ -190,44 +190,46 @@ var _escData = map[string]*_escFile{
|
||||
|
||||
"/helpers.js": {
|
||||
local: "js/helpers.js",
|
||||
size: 6729,
|
||||
size: 7195,
|
||||
modtime: 0,
|
||||
compressed: `
|
||||
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
|
||||
AAA=
|
||||
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
|
||||
`,
|
||||
},
|
||||
|
||||
|
55
main.go
55
main.go
@ -14,23 +14,23 @@ import (
|
||||
|
||||
"github.com/StackExchange/dnscontrol/js"
|
||||
"github.com/StackExchange/dnscontrol/models"
|
||||
"github.com/StackExchange/dnscontrol/nameservers"
|
||||
"github.com/StackExchange/dnscontrol/normalize"
|
||||
"github.com/StackExchange/dnscontrol/providers"
|
||||
"github.com/StackExchange/dnscontrol/providers/config"
|
||||
"github.com/StackExchange/dnscontrol/web"
|
||||
|
||||
//Define all known providers here. They should each register themselves with the providers package via init function.
|
||||
_ "github.com/StackExchange/dnscontrol/providers/activedir"
|
||||
_ "github.com/StackExchange/dnscontrol/providers/bind"
|
||||
_ "github.com/StackExchange/dnscontrol/providers/cloudflare"
|
||||
_ "github.com/StackExchange/dnscontrol/providers/gandi"
|
||||
_ "github.com/StackExchange/dnscontrol/providers/google"
|
||||
_ "github.com/StackExchange/dnscontrol/providers/namecheap"
|
||||
_ "github.com/StackExchange/dnscontrol/providers/namedotcom"
|
||||
_ "github.com/StackExchange/dnscontrol/providers/route53"
|
||||
)
|
||||
|
||||
//go:generate esc -modtime 0 -o js/static.go -pkg js -include helpers\.js -ignore go -prefix js js
|
||||
//go:generate esc -modtime 0 -o web/static.go -pkg web -include=bundle\.js -ignore node_modules -prefix web web
|
||||
|
||||
// One of these config options must be set.
|
||||
var jsFile = flag.String("js", "dnsconfig.js", "Javascript file containing dns config")
|
||||
@ -56,10 +56,6 @@ func main() {
|
||||
fmt.Println(versionString())
|
||||
return
|
||||
}
|
||||
if command == "web" {
|
||||
runWebServer()
|
||||
return
|
||||
}
|
||||
|
||||
var dnsConfig *models.DNSConfig
|
||||
if *stdin {
|
||||
@ -144,21 +140,25 @@ func main() {
|
||||
continue
|
||||
}
|
||||
fmt.Printf("******************** Domain: %s\n", domain.Name)
|
||||
for pi, prov := range domain.Dsps {
|
||||
|
||||
nsList, err := nameservers.DetermineNameservers(domain, 0, dsps)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
domain.Nameservers = nsList
|
||||
nameservers.AddNSRecords(domain)
|
||||
for prov := range domain.DNSProviders {
|
||||
dc, err := domain.Copy()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
shouldrun := shouldRunProvider(prov)
|
||||
if shouldrun {
|
||||
fmt.Printf("----- DNS Provider: %s\n", prov)
|
||||
} else {
|
||||
if pi == 0 {
|
||||
fmt.Printf("----- DNS Provider: %s (read-only)\n", prov)
|
||||
} else {
|
||||
fmt.Printf("----- DNS Provider: %s (skipping)\n", prov)
|
||||
}
|
||||
shouldrun := shouldRunProvider(prov, dc)
|
||||
statusLbl := ""
|
||||
if !shouldrun {
|
||||
statusLbl = "(skipping)"
|
||||
}
|
||||
fmt.Printf("----- DNS Provider: %s%s\n", prov, statusLbl)
|
||||
if !shouldrun {
|
||||
continue
|
||||
}
|
||||
dsp, ok := dsps[prov]
|
||||
if !ok {
|
||||
@ -170,14 +170,10 @@ func main() {
|
||||
fmt.Printf("Error getting corrections: %s\n", err)
|
||||
continue DomainLoop
|
||||
}
|
||||
storeNameservers(dc, domain)
|
||||
if !shouldrun {
|
||||
continue
|
||||
}
|
||||
totalCorrections += len(corrections)
|
||||
anyErrors = printOrRunCorrections(corrections, command) || anyErrors
|
||||
}
|
||||
if !shouldRunProvider(domain.Registrar) {
|
||||
if run := shouldRunProvider(domain.Registrar, domain); !run {
|
||||
continue
|
||||
}
|
||||
fmt.Printf("----- Registrar: %s\n", domain.Registrar)
|
||||
@ -252,12 +248,12 @@ func printOrRunCorrections(corrections []*models.Correction, command string) (an
|
||||
return anyErrors
|
||||
}
|
||||
|
||||
func shouldRunProvider(p string) bool {
|
||||
func shouldRunProvider(p string, dc *models.DomainConfig) bool {
|
||||
if *flagProviders == "all" {
|
||||
return true
|
||||
}
|
||||
if *flagProviders == "" {
|
||||
return p != "bind"
|
||||
return (p != "bind")
|
||||
// NOTE(tlim): Hardcoding bind is a hacky way to make it off by default.
|
||||
// As a result, bind only runs if you list it in -providers or use
|
||||
// -providers=all.
|
||||
@ -290,17 +286,6 @@ func shouldRunDomain(d string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func storeNameservers(from, to *models.DomainConfig) {
|
||||
if len(to.Nameservers) == 0 && len(from.Nameservers) > 0 {
|
||||
to.Nameservers = from.Nameservers
|
||||
}
|
||||
}
|
||||
|
||||
func runWebServer() {
|
||||
fmt.Printf("Running Webserver on :8080 (js = %s , creds = %s)", *jsFile, *configFile)
|
||||
web.Serve(*jsFile, *configFile, *devMode)
|
||||
}
|
||||
|
||||
// Version management. 2 Goals:
|
||||
// 1. Someone who just does "go get" has at least some information.
|
||||
// 2. If built with build.sh, more specific build information gets put in.
|
||||
|
@ -10,15 +10,15 @@ import (
|
||||
"reflect"
|
||||
"strconv"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
"github.com/StackExchange/dnscontrol/transform"
|
||||
"github.com/miekg/dns"
|
||||
)
|
||||
|
||||
const DefaultTTL = uint32(300)
|
||||
|
||||
type DNSConfig struct {
|
||||
Registrars []*RegistrarConfig `json:"registrars"`
|
||||
DNSProviders []*DNSProviderConfig `json:"dns_service_providers"`
|
||||
DNSProviders []*DNSProviderConfig `json:"dns_providers"`
|
||||
Domains []*DomainConfig `json:"domains"`
|
||||
}
|
||||
|
||||
@ -138,14 +138,22 @@ type Nameserver struct {
|
||||
Target string `json:"target"`
|
||||
}
|
||||
|
||||
func StringsToNameservers(nss []string) []*Nameserver {
|
||||
nservers := []*Nameserver{}
|
||||
for _, ns := range nss {
|
||||
nservers = append(nservers, &Nameserver{Name: ns})
|
||||
}
|
||||
return nservers
|
||||
}
|
||||
|
||||
type DomainConfig struct {
|
||||
Name string `json:"name"` // NO trailing "."
|
||||
Registrar string `json:"registrar"`
|
||||
Dsps []string `json:"dsps"`
|
||||
Metadata map[string]string `json:"meta,omitempty"`
|
||||
Records []*RecordConfig `json:"records"`
|
||||
Nameservers []*Nameserver `json:"nameservers,omitempty"`
|
||||
KeepUnknown bool `json:"keepunknown"`
|
||||
Name string `json:"name"` // NO trailing "."
|
||||
Registrar string `json:"registrar"`
|
||||
DNSProviders map[string]int `json:"dnsProviders"`
|
||||
Metadata map[string]string `json:"meta,omitempty"`
|
||||
Records []*RecordConfig `json:"records"`
|
||||
Nameservers []*Nameserver `json:"nameservers,omitempty"`
|
||||
KeepUnknown bool `json:"keepunknown"`
|
||||
}
|
||||
|
||||
func (dc *DomainConfig) Copy() (*DomainConfig, error) {
|
||||
|
69
nameservers/nameservers.go
Normal file
69
nameservers/nameservers.go
Normal file
@ -0,0 +1,69 @@
|
||||
//Package nameservers provides logic for dynamically finding nameservers for a domain, and configuring NS records for them.
|
||||
package nameservers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/StackExchange/dnscontrol/models"
|
||||
"github.com/StackExchange/dnscontrol/providers"
|
||||
"github.com/miekg/dns/dnsutil"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
//DetermineNameservers will find all nameservers we should use for a domain. It follows the following rules:
|
||||
//1. All explicitly defined NAMESERVER records will be used.
|
||||
//2. Each DSP declares how many nameservers to use. Default is all. 0 indicates to use none.
|
||||
func DetermineNameservers(dc *models.DomainConfig, maxNS int, dsps map[string]providers.DNSServiceProvider) ([]*models.Nameserver, error) {
|
||||
//always take explicit
|
||||
ns := dc.Nameservers
|
||||
for dsp, n := range dc.DNSProviders {
|
||||
if n == 0 {
|
||||
continue
|
||||
}
|
||||
fmt.Printf("----- Getting nameservers from: %s\n", dsp)
|
||||
p, ok := dsps[dsp]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("DNS provider %s not declared", dsp)
|
||||
}
|
||||
nss, err := p.GetNameservers(dc.Name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
take := len(nss)
|
||||
if n > 0 && n < take {
|
||||
take = n
|
||||
}
|
||||
for i := 0; i < take; i++ {
|
||||
ns = append(ns, nss[i])
|
||||
}
|
||||
}
|
||||
return ns, nil
|
||||
}
|
||||
|
||||
//AddNSRecords creates NS records on a domain corresponding to the nameservers specified.
|
||||
func AddNSRecords(dc *models.DomainConfig) {
|
||||
ttl := uint32(300)
|
||||
if ttls, ok := dc.Metadata["ns_ttl"]; ok {
|
||||
t, err := strconv.ParseUint(ttls, 10, 32)
|
||||
if err != nil {
|
||||
fmt.Printf("WARNING: ns_ttl fpr %s (%s) is not a valid int", dc.Name, ttls)
|
||||
} else {
|
||||
ttl = uint32(t)
|
||||
}
|
||||
}
|
||||
for _, ns := range dc.Nameservers {
|
||||
rc := &models.RecordConfig{
|
||||
Type: "NS",
|
||||
Name: "@",
|
||||
Target: ns.Name,
|
||||
Metadata: map[string]string{},
|
||||
TTL: ttl,
|
||||
}
|
||||
if !strings.HasSuffix(rc.Target, ".") {
|
||||
rc.Target += "."
|
||||
}
|
||||
rc.NameFQDN = dnsutil.AddOrigin(rc.Name, dc.Name)
|
||||
dc.Records = append(dc.Records, rc)
|
||||
}
|
||||
}
|
@ -50,16 +50,16 @@ func assert_valid_ipv6(label string) error {
|
||||
}
|
||||
|
||||
// assert_valid_cname_target returns 1 if target is not valid for cnames.
|
||||
func assert_valid_target(label string) error {
|
||||
if label == "@" {
|
||||
func assert_valid_target(target string) error {
|
||||
if target == "@" {
|
||||
return nil
|
||||
}
|
||||
if len(label) < 1 {
|
||||
return fmt.Errorf("WARNING: null label.")
|
||||
if len(target) < 1 {
|
||||
return fmt.Errorf("WARNING: null target.")
|
||||
}
|
||||
// If it containts a ".", it must end in a ".".
|
||||
if strings.ContainsRune(label, '.') && label[len(label)-1] != '.' {
|
||||
return fmt.Errorf("WARNING: label (%v) includes a (.), must end with a (.)", label)
|
||||
if strings.ContainsRune(target, '.') && target[len(target)-1] != '.' {
|
||||
return fmt.Errorf("WARNING: target (%v) includes a (.), must end with a (.)", target)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@ -112,6 +112,9 @@ func validateTargets(rec *models.RecordConfig, domain_name string) (errs []error
|
||||
check(assert_no_enddot(label))
|
||||
check(assert_no_underscores(label))
|
||||
check(assert_valid_target(target))
|
||||
if label == "@" {
|
||||
check(fmt.Errorf("cannot create NS record for bare domain. Use NAMESERVER instead"))
|
||||
}
|
||||
case "TXT", "IMPORT_TRANSFORM":
|
||||
default:
|
||||
errs = append(errs, fmt.Errorf("Unimplemented record type (%v) domain=%v name=%v",
|
||||
@ -197,7 +200,6 @@ func NormalizeAndValidateConfig(config *models.DNSConfig) (errs []error) {
|
||||
|
||||
// Normalize Records.
|
||||
for _, rec := range domain.Records {
|
||||
|
||||
// Validate the unmodified inputs:
|
||||
if err := validateRecordTypes(rec, domain.Name); err != nil {
|
||||
errs = append(errs, err)
|
||||
|
@ -1,8 +1,9 @@
|
||||
package normalize
|
||||
|
||||
import (
|
||||
"github.com/StackExchange/dnscontrol/models"
|
||||
"testing"
|
||||
|
||||
"github.com/StackExchange/dnscontrol/models"
|
||||
)
|
||||
|
||||
func Test_assert_no_enddot(t *testing.T) {
|
||||
@ -105,6 +106,20 @@ func Test_transform_cname(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestNSAtRoot(t *testing.T) {
|
||||
//do not allow ns records for @
|
||||
rec := &models.RecordConfig{Name: "test", Type: "NS", Target: "ns1.name.com."}
|
||||
errs := validateTargets(rec, "foo.com")
|
||||
if len(errs) > 0 {
|
||||
t.Error("Expect no error with ns record on subdomain")
|
||||
}
|
||||
rec.Name = "@"
|
||||
errs = validateTargets(rec, "foo.com")
|
||||
if len(errs) != 1 {
|
||||
t.Error("Expect error with ns record on @")
|
||||
}
|
||||
}
|
||||
|
||||
func TestTransforms(t *testing.T) {
|
||||
var tests = []struct {
|
||||
givenIP string
|
||||
|
@ -24,6 +24,11 @@ type RecordConfigJson struct {
|
||||
TTL uint32 `json:"timetolive"`
|
||||
}
|
||||
|
||||
func (c *adProvider) GetNameservers(string) ([]*models.Nameserver, error) {
|
||||
//TODO: If using AD for publicly hosted zones, probably pull these from config.
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// GetDomainCorrections gets existing records, diffs them against existing, and returns corrections.
|
||||
func (c *adProvider) GetDomainCorrections(dc *models.DomainConfig) ([]*models.Correction, error) {
|
||||
|
||||
|
@ -49,8 +49,9 @@ func (s SoaInfo) String() string {
|
||||
}
|
||||
|
||||
type Bind struct {
|
||||
Default_ns []string `json:"default_ns"`
|
||||
Default_Soa SoaInfo `json:"default_soa"`
|
||||
Default_NS []string `json:"default_ns"`
|
||||
Default_Soa SoaInfo `json:"default_soa"`
|
||||
nameservers []*models.Nameserver `json:"-"`
|
||||
}
|
||||
|
||||
var bindBaseDir = flag.String("bindtree", "zones", "BIND: Directory that stores BIND zonefiles.")
|
||||
@ -138,18 +139,8 @@ func makeDefaultSOA(info SoaInfo, origin string) *models.RecordConfig {
|
||||
return &soa_rec
|
||||
}
|
||||
|
||||
func makeDefaultNS(origin string, names []string) []*models.RecordConfig {
|
||||
var result []*models.RecordConfig
|
||||
for _, n := range names {
|
||||
rc := &models.RecordConfig{
|
||||
Type: "NS",
|
||||
Name: "@",
|
||||
Target: n,
|
||||
}
|
||||
rc.NameFQDN = dnsutil.AddOrigin(rc.Name, origin)
|
||||
result = append(result, rc)
|
||||
}
|
||||
return result
|
||||
func (c *Bind) GetNameservers(string) ([]*models.Nameserver, error) {
|
||||
return c.nameservers, nil
|
||||
}
|
||||
|
||||
func (c *Bind) GetDomainCorrections(dc *models.DomainConfig) ([]*models.Correction, error) {
|
||||
@ -207,11 +198,6 @@ func (c *Bind) GetDomainCorrections(dc *models.DomainConfig) ([]*models.Correcti
|
||||
}
|
||||
}
|
||||
|
||||
// Add NS records:
|
||||
if len(c.Default_ns) != 0 && !dc.HasRecordTypeName("NS", "@") {
|
||||
expectedRecords = append(expectedRecords, makeDefaultNS(dc.Name, c.Default_ns)...)
|
||||
dc.Records = append(dc.Records, makeDefaultNS(dc.Name, c.Default_ns)...)
|
||||
}
|
||||
// Add SOA record:
|
||||
if !dc.HasRecordTypeName("SOA", "@") {
|
||||
expectedRecords = append(expectedRecords, soa_rec)
|
||||
@ -286,7 +272,7 @@ func (c *Bind) GetDomainCorrections(dc *models.DomainConfig) ([]*models.Correcti
|
||||
}
|
||||
|
||||
func initBind(config map[string]string, providermeta json.RawMessage) (providers.DNSServiceProvider, error) {
|
||||
// m -- the json blob from creds.json
|
||||
// config -- the key/values from creds.json
|
||||
// meta -- the json blob from NewReq('name', 'TYPE', meta)
|
||||
|
||||
api := &Bind{}
|
||||
@ -296,6 +282,7 @@ func initBind(config map[string]string, providermeta json.RawMessage) (providers
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
api.nameservers = models.StringsToNameservers(api.Default_NS)
|
||||
return api, nil
|
||||
}
|
||||
|
||||
|
@ -115,21 +115,20 @@ func TestWriteZoneFileOrder(t *testing.T) {
|
||||
r, _ := dns.NewRR(fmt.Sprintf("%s 300 IN A 1.2.3.%d", name, i))
|
||||
records = append(records, r)
|
||||
}
|
||||
records[0].Header().Name = "stackoverflow.com."
|
||||
records[1].Header().Name = "@"
|
||||
|
||||
buf := &bytes.Buffer{}
|
||||
WriteZoneFile(buf, records, "stackoverflow.com.", 300)
|
||||
// Compare
|
||||
if buf.String() != testdataOrder {
|
||||
t.Log("Found:")
|
||||
t.Log(buf.String())
|
||||
t.Log("Expected:")
|
||||
t.Log(testdataOrder)
|
||||
t.Fatalf("Zone file does not match.")
|
||||
}
|
||||
parseAndRegen(t, buf, testdataOrder)
|
||||
|
||||
// Now shuffle the list many times and make sure it still works:
|
||||
|
||||
for iteration := 5; iteration > 0; iteration-- {
|
||||
// Randomize the list:
|
||||
perm := rand.Perm(len(records))
|
||||
|
@ -8,11 +8,11 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/miekg/dns/dnsutil"
|
||||
"github.com/StackExchange/dnscontrol/models"
|
||||
"github.com/StackExchange/dnscontrol/providers"
|
||||
"github.com/StackExchange/dnscontrol/providers/diff"
|
||||
"github.com/StackExchange/dnscontrol/transform"
|
||||
"github.com/miekg/dns/dnsutil"
|
||||
)
|
||||
|
||||
/*
|
||||
@ -38,7 +38,7 @@ type CloudflareApi struct {
|
||||
ApiKey string `json:"apikey"`
|
||||
ApiUser string `json:"apiuser"`
|
||||
domainIndex map[string]string
|
||||
nameservers map[string][]*models.Nameserver
|
||||
nameservers map[string][]string
|
||||
ipConversions []transform.IpConversion
|
||||
secretIPs []net.IP
|
||||
ignoredLabels []string
|
||||
@ -53,6 +53,18 @@ func labelMatches(label string, matches []string) bool {
|
||||
}
|
||||
return false
|
||||
}
|
||||
func (c *CloudflareApi) GetNameservers(domain string) ([]*models.Nameserver, error) {
|
||||
if c.domainIndex == nil {
|
||||
if err := c.fetchDomainList(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
ns, ok := c.nameservers[domain]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("Nameservers for %s not found in cloudflare account", domain)
|
||||
}
|
||||
return models.StringsToNameservers(ns), nil
|
||||
}
|
||||
|
||||
func (c *CloudflareApi) GetDomainCorrections(dc *models.DomainConfig) ([]*models.Correction, error) {
|
||||
if c.domainIndex == nil {
|
||||
@ -64,8 +76,6 @@ func (c *CloudflareApi) GetDomainCorrections(dc *models.DomainConfig) ([]*models
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("%s not listed in zones for cloudflare account", dc.Name)
|
||||
}
|
||||
|
||||
dc.Nameservers = c.nameservers[dc.Name]
|
||||
if err := c.preprocessConfig(dc); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -94,6 +104,12 @@ func (c *CloudflareApi) GetDomainCorrections(dc *models.DomainConfig) ([]*models
|
||||
log.Fatalf("FATAL: dnsconfig contains label that matches ignored_labels: %#v is in %v)\n", rec.Name, c.ignoredLabels)
|
||||
// Since we log.Fatalf, we don't need to be clean here.
|
||||
}
|
||||
if rec.Type == "NS" && rec.NameFQDN == dc.Name {
|
||||
if !strings.HasSuffix(rec.Target, ".ns.cloudflare.com.") {
|
||||
log.Printf("Warning: cloudflare does not support modifying NS records on base domain. %s will not be added.", rec.Target)
|
||||
}
|
||||
continue
|
||||
}
|
||||
expectedRecords = append(expectedRecords, recordWrapper{rec})
|
||||
}
|
||||
_, create, del, mod := diff.IncrementalDiff(records, expectedRecords)
|
||||
|
@ -20,7 +20,7 @@ const (
|
||||
// get list of domains for account. Cache so the ids can be looked up from domain name
|
||||
func (c *CloudflareApi) fetchDomainList() error {
|
||||
c.domainIndex = map[string]string{}
|
||||
c.nameservers = map[string][]*models.Nameserver{}
|
||||
c.nameservers = map[string][]string{}
|
||||
page := 1
|
||||
for {
|
||||
zr := &zoneResponse{}
|
||||
@ -34,7 +34,7 @@ func (c *CloudflareApi) fetchDomainList() error {
|
||||
for _, zone := range zr.Result {
|
||||
c.domainIndex[zone.Name] = zone.ID
|
||||
for _, ns := range zone.Nameservers {
|
||||
c.nameservers[zone.Name] = append(c.nameservers[zone.Name], &models.Nameserver{Name: ns})
|
||||
c.nameservers[zone.Name] = append(c.nameservers[zone.Name], ns)
|
||||
}
|
||||
}
|
||||
ri := zr.ResultInfo
|
||||
|
@ -10,6 +10,7 @@ import (
|
||||
"github.com/StackExchange/dnscontrol/providers"
|
||||
"github.com/StackExchange/dnscontrol/providers/diff"
|
||||
|
||||
gandidomain "github.com/prasmussen/gandi-api/domain"
|
||||
gandirecord "github.com/prasmussen/gandi-api/domain/zone/record"
|
||||
)
|
||||
|
||||
@ -72,31 +73,37 @@ func (c *cfRecord) GetComparisionData() string {
|
||||
return fmt.Sprintf("%d", c.Ttl)
|
||||
}
|
||||
|
||||
func (c *GandiApi) GetDomainCorrections(dc *models.DomainConfig) ([]*models.Correction, error) {
|
||||
|
||||
if c.domainIndex == nil {
|
||||
if err := c.fetchDomainList(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
func (c *GandiApi) getDomainInfo(domain string) (*gandidomain.DomainInfo, error) {
|
||||
if err := c.fetchDomainList(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_, ok := c.domainIndex[dc.Name]
|
||||
_, ok := c.domainIndex[domain]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("%s not listed in zones for gandi account", dc.Name)
|
||||
return nil, fmt.Errorf("%s not listed in zones for gandi account", domain)
|
||||
}
|
||||
return c.fetchDomainInfo(domain)
|
||||
}
|
||||
|
||||
domaininfo, err := c.fetchDomainInfo(dc.Name)
|
||||
func (c *GandiApi) GetNameservers(domain string) ([]*models.Nameserver, error) {
|
||||
domaininfo, err := c.getDomainInfo(domain)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ns := []*models.Nameserver{}
|
||||
for _, nsname := range domaininfo.Nameservers {
|
||||
dc.Nameservers = append(dc.Nameservers, &models.Nameserver{Name: nsname})
|
||||
ns = append(ns, &models.Nameserver{Name: nsname})
|
||||
}
|
||||
return ns, nil
|
||||
}
|
||||
func (c *GandiApi) GetDomainCorrections(dc *models.DomainConfig) ([]*models.Correction, error) {
|
||||
domaininfo, err := c.getDomainInfo(dc.Name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
foundRecords, err := c.getZoneRecords(domaininfo.ZoneId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Convert to []diff.Records and compare:
|
||||
foundDiffRecords := make([]diff.Record, len(foundRecords))
|
||||
for i, rec := range foundRecords {
|
||||
|
@ -16,6 +16,9 @@ import (
|
||||
|
||||
// fetchDomainList gets list of domains for account. Cache ids for easy lookup.
|
||||
func (c *GandiApi) fetchDomainList() error {
|
||||
if c.domainIndex != nil {
|
||||
return nil
|
||||
}
|
||||
c.domainIndex = map[string]int64{}
|
||||
gc := gandiclient.New(c.ApiKey, gandiclient.Production)
|
||||
domain := gandidomain.New(gc)
|
||||
|
217
providers/google/google.go
Normal file
217
providers/google/google.go
Normal file
@ -0,0 +1,217 @@
|
||||
package google
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/StackExchange/dnscontrol/models"
|
||||
"github.com/StackExchange/dnscontrol/providers"
|
||||
|
||||
"github.com/StackExchange/dnscontrol/providers/diff"
|
||||
"golang.org/x/oauth2"
|
||||
gauth "golang.org/x/oauth2/google"
|
||||
"google.golang.org/api/dns/v1"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func init() {
|
||||
providers.RegisterDomainServiceProviderType("GCLOUD", New)
|
||||
}
|
||||
|
||||
type gcloud struct {
|
||||
client *dns.Service
|
||||
project string
|
||||
zones map[string]*dns.ManagedZone
|
||||
}
|
||||
|
||||
func New(cfg map[string]string, _ json.RawMessage) (providers.DNSServiceProvider, error) {
|
||||
for _, key := range []string{"clientId", "clientSecret", "refreshToken", "project"} {
|
||||
if cfg[key] == "" {
|
||||
return nil, fmt.Errorf("%s required for google cloud provider", key)
|
||||
}
|
||||
}
|
||||
ocfg := &oauth2.Config{
|
||||
Endpoint: gauth.Endpoint,
|
||||
ClientID: cfg["clientId"],
|
||||
ClientSecret: cfg["clientSecret"],
|
||||
}
|
||||
tok := &oauth2.Token{
|
||||
RefreshToken: cfg["refreshToken"],
|
||||
}
|
||||
client := ocfg.Client(context.Background(), tok)
|
||||
dcli, err := dns.New(client)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &gcloud{
|
||||
client: dcli,
|
||||
project: cfg["project"],
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (g *gcloud) getZone(domain string) (*dns.ManagedZone, error) {
|
||||
if g.zones == nil {
|
||||
g.zones = map[string]*dns.ManagedZone{}
|
||||
pageToken := ""
|
||||
for {
|
||||
resp, err := g.client.ManagedZones.List(g.project).PageToken(pageToken).Do()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, z := range resp.ManagedZones {
|
||||
g.zones[z.DnsName] = z
|
||||
}
|
||||
if pageToken = resp.NextPageToken; pageToken == "" {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if g.zones[domain+"."] == nil {
|
||||
return nil, fmt.Errorf("Domain %s not found in gcloud account", domain)
|
||||
}
|
||||
return g.zones[domain+"."], nil
|
||||
}
|
||||
|
||||
func (g *gcloud) GetNameservers(domain string) ([]*models.Nameserver, error) {
|
||||
zone, err := g.getZone(domain)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return models.StringsToNameservers(zone.NameServers), nil
|
||||
}
|
||||
|
||||
type key struct {
|
||||
Type string
|
||||
Name string
|
||||
}
|
||||
|
||||
func keyFor(r *dns.ResourceRecordSet) key {
|
||||
return key{Type: r.Type, Name: r.Name}
|
||||
}
|
||||
func keyForRec(r *models.RecordConfig) key {
|
||||
return key{Type: r.Type, Name: r.NameFQDN + "."}
|
||||
}
|
||||
|
||||
func (g *gcloud) GetDomainCorrections(dc *models.DomainConfig) ([]*models.Correction, error) {
|
||||
rrs, zoneName, err := g.getRecords(dc.Name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
//convert to dnscontrol RecordConfig format
|
||||
existingRecords := []diff.Record{}
|
||||
oldRRs := map[key]*dns.ResourceRecordSet{}
|
||||
for _, set := range rrs {
|
||||
nameWithoutDot := set.Name
|
||||
if strings.HasSuffix(nameWithoutDot, ".") {
|
||||
nameWithoutDot = nameWithoutDot[:len(nameWithoutDot)-1]
|
||||
}
|
||||
oldRRs[keyFor(set)] = set
|
||||
for _, rec := range set.Rrdatas {
|
||||
r := &models.RecordConfig{
|
||||
NameFQDN: nameWithoutDot,
|
||||
Type: set.Type,
|
||||
Target: rec,
|
||||
TTL: uint32(set.Ttl),
|
||||
}
|
||||
existingRecords = append(existingRecords, r)
|
||||
}
|
||||
}
|
||||
|
||||
w := []diff.Record{}
|
||||
for _, want := range dc.Records {
|
||||
if want.TTL == 0 {
|
||||
want.TTL = 300
|
||||
}
|
||||
if want.Type == "MX" {
|
||||
want.Target = fmt.Sprintf("%d %s", want.Priority, want.Target)
|
||||
want.Priority = 0
|
||||
} else if want.Type == "TXT" {
|
||||
//add quotes to txts
|
||||
want.Target = fmt.Sprintf(`"%s"`, want.Target)
|
||||
}
|
||||
w = append(w, want)
|
||||
}
|
||||
|
||||
// first collect keys that have changed
|
||||
_, create, delete, modify := diff.IncrementalDiff(existingRecords, w)
|
||||
changedKeys := map[key]bool{}
|
||||
desc := ""
|
||||
for _, c := range create {
|
||||
desc += fmt.Sprintln(c)
|
||||
changedKeys[keyForRec(c.Desired.(*models.RecordConfig))] = true
|
||||
}
|
||||
for _, d := range delete {
|
||||
desc += fmt.Sprintln(d)
|
||||
changedKeys[keyForRec(d.Existing.(*models.RecordConfig))] = true
|
||||
}
|
||||
for _, m := range modify {
|
||||
desc += fmt.Sprintln(m)
|
||||
changedKeys[keyForRec(m.Existing.(*models.RecordConfig))] = true
|
||||
}
|
||||
if len(changedKeys) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
chg := &dns.Change{Kind: "dns#change"}
|
||||
for ck := range changedKeys {
|
||||
// remove old version (if present)
|
||||
if old, ok := oldRRs[ck]; ok {
|
||||
chg.Deletions = append(chg.Deletions, old)
|
||||
}
|
||||
//collect records to replace with
|
||||
newRRs := &dns.ResourceRecordSet{
|
||||
Name: ck.Name,
|
||||
Type: ck.Type,
|
||||
Kind: "dns#resourceRecordSet",
|
||||
}
|
||||
for _, r := range dc.Records {
|
||||
if keyForRec(r) == ck {
|
||||
newRRs.Rrdatas = append(newRRs.Rrdatas, r.Target)
|
||||
newRRs.Ttl = int64(r.TTL)
|
||||
}
|
||||
}
|
||||
if len(newRRs.Rrdatas) > 0 {
|
||||
chg.Additions = append(chg.Additions, newRRs)
|
||||
}
|
||||
}
|
||||
|
||||
runChange := func() error {
|
||||
_, err := g.client.Changes.Create(g.project, zoneName, chg).Do()
|
||||
return err
|
||||
}
|
||||
return []*models.Correction{
|
||||
{
|
||||
Msg: desc,
|
||||
F: runChange,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (g *gcloud) getRecords(domain string) ([]*dns.ResourceRecordSet, string, error) {
|
||||
zone, err := g.getZone(domain)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
pageToken := ""
|
||||
sets := []*dns.ResourceRecordSet{}
|
||||
for {
|
||||
call := g.client.ResourceRecordSets.List(g.project, zone.Name)
|
||||
if pageToken != "" {
|
||||
call = call.PageToken(pageToken)
|
||||
}
|
||||
resp, err := call.Do()
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
for _, rrs := range resp.Rrsets {
|
||||
if rrs.Type == "SOA" {
|
||||
continue
|
||||
}
|
||||
sets = append(sets, rrs)
|
||||
}
|
||||
if pageToken = resp.NextPageToken; pageToken == "" {
|
||||
break
|
||||
}
|
||||
}
|
||||
return sets, zone.Name, nil
|
||||
}
|
@ -9,14 +9,44 @@ import (
|
||||
"github.com/StackExchange/dnscontrol/models"
|
||||
)
|
||||
|
||||
func (n *nameDotCom) GetRegistrarCorrections(dc *models.DomainConfig) ([]*models.Correction, error) {
|
||||
foundNameservers, err := n.getNameservers(dc.Name)
|
||||
var nsRegex = regexp.MustCompile(`ns([1-4])[a-z]{3}\.name\.com`)
|
||||
|
||||
func (n *nameDotCom) GetNameservers(domain string) ([]*models.Nameserver, error) {
|
||||
//This is an interesting edge case. Name.com expects you to SET the nameservers to ns[1-4].name.com,
|
||||
//but it will internally set it to ns1xyz.name.com, where xyz is a uniqueish 3 letters.
|
||||
//In order to avoid endless loops, we will use the unique nameservers if present, or else the generic ones if not.
|
||||
nss, err := n.getNameserversRaw(domain)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if defaultNsRegexp.MatchString(foundNameservers) {
|
||||
foundNameservers = "ns1.name.com,ns2.name.com,ns3.name.com,ns4.name.com"
|
||||
toUse := []string{"ns1.name.com", "ns2.name.com", "ns3.name.com", "ns4.name.com"}
|
||||
for _, ns := range nss {
|
||||
if matches := nsRegex.FindStringSubmatch(ns); len(matches) == 2 && len(matches[1]) == 1 {
|
||||
idx := matches[1][0] - '1' //regex ensures proper range
|
||||
toUse[idx] = matches[0]
|
||||
}
|
||||
}
|
||||
return models.StringsToNameservers(toUse), nil
|
||||
}
|
||||
|
||||
func (n *nameDotCom) getNameserversRaw(domain string) ([]string, error) {
|
||||
result := &getDomainResult{}
|
||||
if err := n.get(apiGetDomain(domain), result); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := result.getErr(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sort.Strings(result.Nameservers)
|
||||
return result.Nameservers, nil
|
||||
}
|
||||
|
||||
func (n *nameDotCom) GetRegistrarCorrections(dc *models.DomainConfig) ([]*models.Correction, error) {
|
||||
nss, err := n.getNameserversRaw(dc.Name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
foundNameservers := strings.Join(nss, ",")
|
||||
expected := []string{}
|
||||
for _, ns := range dc.Nameservers {
|
||||
name := strings.TrimRight(ns.Name, ".")
|
||||
@ -52,19 +82,6 @@ type getDomainResult struct {
|
||||
Nameservers []string `json:"nameservers"`
|
||||
}
|
||||
|
||||
// returns comma joined list of nameservers (in alphabetical order)
|
||||
func (n *nameDotCom) getNameservers(domain string) (string, error) {
|
||||
result := &getDomainResult{}
|
||||
if err := n.get(apiGetDomain(domain), result); err != nil {
|
||||
return "", err
|
||||
}
|
||||
if err := result.getErr(); err != nil {
|
||||
return "", err
|
||||
}
|
||||
sort.Strings(result.Nameservers)
|
||||
return strings.Join(result.Nameservers, ","), nil
|
||||
}
|
||||
|
||||
func (n *nameDotCom) updateNameservers(ns []string, domain string) func() error {
|
||||
return func() error {
|
||||
dat := struct {
|
||||
|
@ -4,6 +4,7 @@ import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/StackExchange/dnscontrol/models"
|
||||
@ -30,7 +31,7 @@ func teardown() {
|
||||
server.Close()
|
||||
}
|
||||
|
||||
func TestGetNameservers(t *testing.T) {
|
||||
func TestGetNameserversRaw(t *testing.T) {
|
||||
for i, test := range []struct {
|
||||
givenNs, expected string
|
||||
}{
|
||||
@ -54,7 +55,7 @@ func TestGetNameservers(t *testing.T) {
|
||||
w.Write(domainResponse(test.givenNs))
|
||||
})
|
||||
|
||||
found, err := client.getNameservers("example.tld")
|
||||
found, err := client.getNameserversRaw("example.tld")
|
||||
if err != nil {
|
||||
if test.expected == "ERR" {
|
||||
continue
|
||||
@ -66,7 +67,7 @@ func TestGetNameservers(t *testing.T) {
|
||||
t.Errorf("Expected error on test %d, but was none", i)
|
||||
continue
|
||||
}
|
||||
if found != test.expected {
|
||||
if strings.Join(found, ",") != test.expected {
|
||||
t.Errorf("Test %d: Expected '%s', but found '%s'", i, test.expected, found)
|
||||
}
|
||||
}
|
||||
@ -148,3 +149,34 @@ func domainResponse(ns string) []byte {
|
||||
}
|
||||
|
||||
var nameComError = []byte(`{"result":{"code":251,"message":"Authentication Error - Invalid Username Or Api Token"}}`)
|
||||
|
||||
func TestGetNameservers(t *testing.T) {
|
||||
const d = "ns1.name.com,ns2.name.com,ns3.name.com,ns4.name.com"
|
||||
for i, test := range []struct {
|
||||
givenNs, expected string
|
||||
}{
|
||||
//empty or external dsp, use ns1-4.name.com
|
||||
{"", d},
|
||||
{`"foo.ns.tld","bar.ns.tld"`, d},
|
||||
//if already on name.com, use the existing nameservers
|
||||
{`"ns1aaa.name.com","ns2bbb.name.com","ns3ccc.name.com","ns4ddd.name.com"`, "ns1aaa.name.com,ns2bbb.name.com,ns3ccc.name.com,ns4ddd.name.com"},
|
||||
//also handle half and half
|
||||
{`"ns1aaa.name.com","ns2bbb.name.com","ns3ccc.aws.net","ns4ddd.awsdns.org"`, "ns1aaa.name.com,ns2bbb.name.com,ns3.name.com,ns4.name.com"},
|
||||
{`"nsa.azuredns.com","ns2b.gandhi.net","ns3ccc.name.com","ns4ddd.name.com"`, "ns1.name.com,ns2.name.com,ns3ccc.name.com,ns4ddd.name.com"},
|
||||
} {
|
||||
setup()
|
||||
defer teardown()
|
||||
|
||||
mux.HandleFunc("/domain/get/example.tld", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write(domainResponse(test.givenNs))
|
||||
})
|
||||
found, err := client.GetNameservers("example.tld")
|
||||
if err != nil {
|
||||
t.Errorf("Test %d: %s", i, err)
|
||||
continue
|
||||
}
|
||||
if strings.Join(found, ",") != test.expected {
|
||||
t.Errorf("Test %d: Expected '%s', but found '%s'", i, test.expected, found)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,6 +2,8 @@ package namedotcom
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"strings"
|
||||
|
||||
"github.com/miekg/dns/dnsutil"
|
||||
|
||||
@ -17,7 +19,6 @@ var defaultNameservers = []*models.Nameserver{
|
||||
}
|
||||
|
||||
func (n *nameDotCom) GetDomainCorrections(dc *models.DomainConfig) ([]*models.Correction, error) {
|
||||
dc.Nameservers = defaultNameservers
|
||||
records, err := n.getRecords(dc.Name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -27,12 +28,19 @@ func (n *nameDotCom) GetDomainCorrections(dc *models.DomainConfig) ([]*models.Co
|
||||
actual[i] = records[i]
|
||||
}
|
||||
|
||||
desired := make([]diff.Record, len(dc.Records))
|
||||
for i, rec := range dc.Records {
|
||||
desired := make([]diff.Record, 0, len(dc.Records))
|
||||
for _, rec := range dc.Records {
|
||||
if rec.TTL == 0 {
|
||||
rec.TTL = 300
|
||||
}
|
||||
desired[i] = rec
|
||||
if rec.Type == "NS" && rec.NameFQDN == dc.Name {
|
||||
// name.com does change base domain NS records. dnscontrol will print warnings if you try to set them to anything besides the name.com defaults.
|
||||
if !strings.HasSuffix(rec.Target, ".name.com.") {
|
||||
log.Printf("Warning: name.com does not allow NS records on base domain to be modified. %s will not be added.", rec.Target)
|
||||
}
|
||||
continue
|
||||
}
|
||||
desired = append(desired, rec)
|
||||
}
|
||||
|
||||
_, create, del, mod := diff.IncrementalDiff(actual, desired)
|
||||
@ -117,10 +125,8 @@ func (n *nameDotCom) getRecords(domain string) ([]*nameComRecord, error) {
|
||||
for _, rc := range result.Records {
|
||||
if rc.Type == "CNAME" || rc.Type == "MX" || rc.Type == "NS" {
|
||||
rc.Content = rc.Content + "."
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return result.Records, nil
|
||||
}
|
||||
|
||||
|
@ -15,6 +15,7 @@ type Registrar interface {
|
||||
|
||||
//DNSServiceProvider is able to generate a set of corrections that need to be made to correct records for a domain
|
||||
type DNSServiceProvider interface {
|
||||
GetNameservers(domain string) ([]*models.Nameserver, error)
|
||||
GetDomainCorrections(dc *models.DomainConfig) ([]*models.Correction, error)
|
||||
}
|
||||
|
||||
@ -84,12 +85,10 @@ func CreateDsps(d *models.DNSConfig, providerConfigs map[string]map[string]strin
|
||||
//log.Printf("dsp.Name=%#v\n", dsp.Name)
|
||||
rawMsg, ok := providerConfigs[dsp.Name]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("DNSServiceProvider %s not listed in -providers file.", dsp.Name)
|
||||
return nil, fmt.Errorf("DNSServiceProvider %s not listed in -providers file", dsp.Name)
|
||||
}
|
||||
provider, err := createDNSProvider(dsp.Type, rawMsg, dsp.Metadata)
|
||||
if err != nil {
|
||||
log.Printf("createDNSProvider provider=%#v\n", provider)
|
||||
log.Printf("createDNSProvider err=%#v\n", err)
|
||||
return nil, err
|
||||
}
|
||||
dsps[dsp.Name] = provider
|
||||
@ -104,6 +103,10 @@ func (n None) GetRegistrarCorrections(dc *models.DomainConfig) ([]*models.Correc
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (n None) GetNameservers(string) ([]*models.Nameserver, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (n None) GetDomainCorrections(dc *models.DomainConfig) ([]*models.Correction, error) {
|
||||
return nil, nil
|
||||
}
|
||||
@ -112,8 +115,4 @@ func init() {
|
||||
RegisterRegistrarType("NONE", func(map[string]string) (Registrar, error) {
|
||||
return None{}, nil
|
||||
})
|
||||
RegisterDomainServiceProviderType("NONE", func(map[string]string, json.RawMessage) (DNSServiceProvider, error) {
|
||||
return None{}, nil
|
||||
})
|
||||
|
||||
}
|
||||
|
@ -3,16 +3,17 @@ package route53
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/StackExchange/dnscontrol/models"
|
||||
"github.com/StackExchange/dnscontrol/providers"
|
||||
"github.com/StackExchange/dnscontrol/providers/diff"
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/aws/credentials"
|
||||
"github.com/aws/aws-sdk-go/aws/session"
|
||||
r53 "github.com/aws/aws-sdk-go/service/route53"
|
||||
"github.com/StackExchange/dnscontrol/models"
|
||||
"github.com/StackExchange/dnscontrol/providers"
|
||||
"github.com/StackExchange/dnscontrol/providers/diff"
|
||||
)
|
||||
|
||||
type route53Provider struct {
|
||||
@ -40,12 +41,17 @@ func init() {
|
||||
func sPtr(s string) *string {
|
||||
return &s
|
||||
}
|
||||
|
||||
func (r *route53Provider) getZones() error {
|
||||
if r.zones != nil {
|
||||
return nil
|
||||
}
|
||||
var nextMarker *string
|
||||
r.zones = make(map[string]*r53.HostedZone)
|
||||
for {
|
||||
inp := &r53.ListHostedZonesInput{MaxItems: sPtr("1"), Marker: nextMarker}
|
||||
if nextMarker != nil {
|
||||
fmt.Println(*nextMarker)
|
||||
}
|
||||
inp := &r53.ListHostedZonesInput{Marker: nextMarker}
|
||||
out, err := r.client.ListHostedZones(inp)
|
||||
if err != nil {
|
||||
return err
|
||||
@ -72,12 +78,31 @@ func getKey(r diff.Record) key {
|
||||
return key{r.GetName(), r.GetType()}
|
||||
}
|
||||
|
||||
func (r *route53Provider) GetDomainCorrections(dc *models.DomainConfig) ([]*models.Correction, error) {
|
||||
if r.zones == nil {
|
||||
if err := r.getZones(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
func (r *route53Provider) GetNameservers(domain string) ([]*models.Nameserver, error) {
|
||||
if err := r.getZones(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
zone, ok := r.zones[domain]
|
||||
if !ok {
|
||||
log.Printf("WARNING: Domain %s is not on your route 53 account. Dnscontrol will add it, but you will need to run a second time to configure nameservers properly.", domain)
|
||||
return nil, nil
|
||||
}
|
||||
z, err := r.client.GetHostedZone(&r53.GetHostedZoneInput{Id: zone.Id})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ns := []*models.Nameserver{}
|
||||
for _, nsPtr := range z.DelegationSet.NameServers {
|
||||
ns = append(ns, &models.Nameserver{Name: *nsPtr})
|
||||
}
|
||||
return ns, nil
|
||||
}
|
||||
|
||||
func (r *route53Provider) GetDomainCorrections(dc *models.DomainConfig) ([]*models.Correction, error) {
|
||||
if err := r.getZones(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var corrections = []*models.Correction{}
|
||||
zone, ok := r.zones[dc.Name]
|
||||
// add zone if it doesn't exist
|
||||
@ -108,15 +133,11 @@ func (r *route53Provider) GetDomainCorrections(dc *models.DomainConfig) ([]*mode
|
||||
}
|
||||
|
||||
//convert to dnscontrol RecordConfig format
|
||||
dc.Nameservers = nil
|
||||
var existingRecords = []*models.RecordConfig{}
|
||||
var existingRecords = []diff.Record{}
|
||||
for _, set := range records {
|
||||
for _, rec := range set.ResourceRecords {
|
||||
if *set.Type == "SOA" {
|
||||
continue
|
||||
} else if *set.Type == "NS" && strings.TrimSuffix(*set.Name, ".") == dc.Name {
|
||||
dc.Nameservers = append(dc.Nameservers, &models.Nameserver{Name: strings.TrimSuffix(*rec.Value, ".")})
|
||||
continue
|
||||
}
|
||||
r := &models.RecordConfig{
|
||||
NameFQDN: unescape(set.Name),
|
||||
@ -127,10 +148,7 @@ func (r *route53Provider) GetDomainCorrections(dc *models.DomainConfig) ([]*mode
|
||||
existingRecords = append(existingRecords, r)
|
||||
}
|
||||
}
|
||||
e, w := []diff.Record{}, []diff.Record{}
|
||||
for _, ex := range existingRecords {
|
||||
e = append(e, ex)
|
||||
}
|
||||
w := []diff.Record{}
|
||||
for _, want := range dc.Records {
|
||||
if want.TTL == 0 {
|
||||
want.TTL = 300
|
||||
@ -146,7 +164,7 @@ func (r *route53Provider) GetDomainCorrections(dc *models.DomainConfig) ([]*mode
|
||||
|
||||
//diff
|
||||
changeDesc := ""
|
||||
_, create, delete, modify := diff.IncrementalDiff(e, w)
|
||||
_, create, delete, modify := diff.IncrementalDiff(existingRecords, w)
|
||||
|
||||
namesToUpdate := map[key]bool{}
|
||||
for _, c := range create {
|
||||
|
@ -1,3 +0,0 @@
|
||||
{
|
||||
"presets": ["es2015"]
|
||||
}
|
@ -1,115 +0,0 @@
|
||||
<template>
|
||||
<div class="container">
|
||||
<div class='clearfix'>
|
||||
<h3 class='pull-left'> DNSControl </h3>
|
||||
<button class='btn btn-success pull-right' @click="save" style='margin-top:15px'>Save</button>
|
||||
</div>
|
||||
<multiselect name="domains" :options="domains" placeholder="jump to domain" :selected="null" :multiple="false" :searchable="true" :on-change="domainSelect" :show-labels="false" :reset-after="true"></multiselect>
|
||||
<br/>
|
||||
<multiselect name="vars" :options="vars" placeholder="jump to var" :selected="null" :multiple="false" :searchable="true" :on-change="varSelect" :show-labels="false" :reset-after="true"></multiselect>
|
||||
<br/>
|
||||
<editor :text.once="content" v-ref:editor :on-change="textChanged"></editor>
|
||||
|
||||
<div v-show="error" class="alert alert-danger" role="alert">{{error}}</div>
|
||||
<dns-config v-if="config" :data="config" :selected="lastSelected" ></dns-config>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Multiselect from 'vue-multiselect'
|
||||
import Editor from './Editor'
|
||||
import DnsConfig from './DNSConfig'
|
||||
import 'whatwg-fetch';
|
||||
import _ from 'lodash';
|
||||
window._ = _;
|
||||
|
||||
export default {
|
||||
data:function(){
|
||||
return {
|
||||
domains: [],
|
||||
vars: [],
|
||||
content: window.initialScript,
|
||||
config: null,
|
||||
error: null,
|
||||
lastSelected: ""
|
||||
};
|
||||
},
|
||||
created: function(){
|
||||
this.parse();
|
||||
},
|
||||
methods:{
|
||||
parse: function(){
|
||||
var domainRE = this.buildDomainRegex();
|
||||
var match;
|
||||
var domains = [];
|
||||
while(match = domainRE.exec(this.content)){
|
||||
domains.push(match[2]);
|
||||
}
|
||||
domains.sort(function (a, b) {return a.toLowerCase().localeCompare(b.toLowerCase());});
|
||||
var varRE = this.buildVarRegex();
|
||||
var vars = [];
|
||||
while(match = varRE.exec(this.content)){
|
||||
vars.push(match[1]);
|
||||
}
|
||||
vars.sort(function (a, b) {return a.toLowerCase().localeCompare(b.toLowerCase());});
|
||||
this.domains = domains;
|
||||
this.vars = vars;
|
||||
this.run();
|
||||
},
|
||||
textChanged: function(e){
|
||||
this.content = e;
|
||||
this.parse();
|
||||
},
|
||||
domainSelect: function(val){
|
||||
var searchRegex = this.buildDomainRegex(val);
|
||||
this.$refs.editor.jump(searchRegex);
|
||||
this.lastSelected = val;
|
||||
},
|
||||
varSelect: function(val){
|
||||
var searchRegex = this.buildVarRegex(val);
|
||||
this.$refs.editor.jump(searchRegex);
|
||||
},
|
||||
buildDomainRegex: function(d){
|
||||
// / ^\s*D\s*\(\s*(['"])((?:\\\1|.)*?)\1/gm; //holy crap
|
||||
/*
|
||||
^ //start of line
|
||||
\s*D\s*\(\s* // D( with any whitespace
|
||||
(['"]) // single or double quote (matching group 1)
|
||||
((?:\\\1|.)*?) //voodoo. Everything that is not the same char as the start quote
|
||||
\1 //same quote again
|
||||
|
||||
Turned into string and double escaped slashes below
|
||||
*/
|
||||
if (!d){
|
||||
d = "((?:\\\\\\1|.)*?)"
|
||||
}
|
||||
return new RegExp("^\\s*D\\s*\\(\\s*(['\"])"+d+"\\1","gm");
|
||||
},
|
||||
buildVarRegex: function(v){
|
||||
if (!v){
|
||||
v = "([^\\s=]+)"
|
||||
}
|
||||
return new RegExp("^\s*var "+v,"gm");
|
||||
},
|
||||
run: function(){
|
||||
this.error = null;
|
||||
this.config = null;
|
||||
try{
|
||||
initialize();
|
||||
eval(this.content);
|
||||
this.config= conf;
|
||||
this.stale = false;
|
||||
}catch(e){
|
||||
this.error = e.toString();
|
||||
}
|
||||
},
|
||||
save: function(){
|
||||
fetch('/api/save', {
|
||||
method: 'POST',
|
||||
body: this.content
|
||||
}) //TODO: error handling
|
||||
}
|
||||
},
|
||||
components:{Multiselect,Editor,DnsConfig}
|
||||
}
|
||||
</script>
|
@ -1,57 +0,0 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class='clearfix'>
|
||||
<div class='pull-left'>{{correction.Msg}}
|
||||
<span v-show="!hasRun" class="label label-primary noselect" @click="run">Run!</span>
|
||||
<span v-show="ok" class='glyphicon glyphicon-ok' style='color:rgb(93, 197, 150);'></span>
|
||||
</div>
|
||||
<spinner :loading="running" class='pull-left' height="15px" ></spinner>
|
||||
</div>
|
||||
<alert v-if="error" type="danger" >
|
||||
{{error}}
|
||||
</alert>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import spinner from 'vue-spinner/src/ScaleLoader.vue'
|
||||
import { alert } from 'vue-strap'
|
||||
|
||||
export default{
|
||||
data: function(){return{
|
||||
hasRun: false,
|
||||
running: false,
|
||||
error: "",
|
||||
ok: false
|
||||
};},
|
||||
methods: {
|
||||
run: function(){
|
||||
if (this.hasRun){return;}
|
||||
this.hasRun = true;
|
||||
this.running = true;
|
||||
this.error = "";
|
||||
this.ok = false;
|
||||
var self = this;
|
||||
fetch("/api/run?id="+this.correction.ID,{method: "POST"})
|
||||
.then(function(response) {
|
||||
if (response.status != 200){
|
||||
response.text().then(function(txt){
|
||||
self.error = txt;
|
||||
self.running = false;
|
||||
self.hasRun = false;
|
||||
})
|
||||
return
|
||||
}
|
||||
self.ok= true
|
||||
self.running=false
|
||||
});
|
||||
}
|
||||
},
|
||||
props:{
|
||||
correction: Object
|
||||
},
|
||||
components:{spinner,alert}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
@ -1,41 +0,0 @@
|
||||
<template>
|
||||
<div>
|
||||
<run-results :show.sync="showModal" :config="data" :query="query"></run-results>
|
||||
<domain v-for="domain in sortedDomains" :d="domain" :on-preview="preview"></domain>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import Domain from './Domain'
|
||||
import RunResults from './RunResults'
|
||||
|
||||
export default{
|
||||
data: function(){return{
|
||||
showModal: false,
|
||||
query: {},
|
||||
};},
|
||||
props: {
|
||||
data: Object,
|
||||
selected: String,
|
||||
},
|
||||
computed: {
|
||||
// hack to bring selected domain to the top.
|
||||
// computed property with domains in the correct sort order.
|
||||
sortedDomains: function(){
|
||||
var selected = this.selected;
|
||||
return _.sortBy(this.data.domains,function(d){
|
||||
return d.name != selected;
|
||||
},function(d,i){return i;})
|
||||
}
|
||||
},
|
||||
methods:{
|
||||
preview: function(data){
|
||||
this.query = data;
|
||||
this.showModal = true;
|
||||
}
|
||||
},
|
||||
components: {Domain,RunResults}
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
|
||||
</style>
|
@ -1,88 +0,0 @@
|
||||
<template>
|
||||
<div class='domain'>
|
||||
<div class='row'>
|
||||
<div class='col-md-12' style='padding-left:20px;padding-right:25px;'>
|
||||
<div class='pull-left'><h2>{{d.name}}</h2></div>
|
||||
<div class='meta pull-left'><metadata :meta="d.meta"></metadata></div>
|
||||
<tooltip trigger="hover" placement="bottom" content="Compare data with provider state and view corrections">
|
||||
<div class='pull-right'><button class='btn btn-default btn-domain' @click="preview">Preview</button></div>
|
||||
</tooltip>
|
||||
</div>
|
||||
</div>
|
||||
<tooltip trigger="hover" placement="bottom" :content="regTooltip()">
|
||||
<span :class="['label','noselect',registrarEnabled?'label-success':'label-default']" @click="registrarEnabled = !registrarEnabled">{{d.registrar}}</span>
|
||||
</tooltip>
|
||||
<tooltip v-for="(i,dsp) in d.dsps" trigger="hover" placement="bottom" :content="dspTooltip(i)">
|
||||
<span :class="['label','noselect',dsps[i]?'label-success':'label-default']" @click="dsps.$set(i,!this.dsps[i])">{{dsp}}</span>
|
||||
</tooltip>
|
||||
<record v-for="record in d.records" :record="record"></record>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Metadata from './Metadata'
|
||||
import Record from './Record'
|
||||
import { tooltip } from 'vue-strap'
|
||||
|
||||
export default{
|
||||
name:"domain",
|
||||
props: {
|
||||
d:Object,
|
||||
onPreview: {type:Function, required:true} //accepts a data object and returns a fetch promise
|
||||
},
|
||||
data:function(){return{
|
||||
registrarEnabled: true,
|
||||
dsps: []
|
||||
}},
|
||||
created: function(){
|
||||
for(var i = 0; i<this.d.dsps.length; i++){
|
||||
this.dsps[i] = true;
|
||||
}
|
||||
},
|
||||
methods:{
|
||||
preview: function(){
|
||||
var data = {
|
||||
Domain: this.d.name,
|
||||
Registrar: this.registrarEnabled,
|
||||
Dsps: this.dsps
|
||||
}
|
||||
this.onPreview(data);
|
||||
},
|
||||
regTooltip: function(){
|
||||
return (this.registrarEnabled?"Disable":"Enable")+" checking of registrar entries with "+this.d.registrar;
|
||||
},
|
||||
dspTooltip: function(i){
|
||||
return (this.dsps[i]?"Disable":"Enable")+" checking of domain records with "+this.d.dsps[i];
|
||||
}
|
||||
},
|
||||
components: {Metadata,Record,tooltip}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.domain{
|
||||
padding-bottom: 15px;
|
||||
}
|
||||
.noselect {
|
||||
-webkit-touch-callout: none;
|
||||
-webkit-user-select: none;
|
||||
-khtml-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
.domain:nth-of-type(odd) {
|
||||
background: #e0e0e0;
|
||||
}
|
||||
.label {
|
||||
margin-right: 4px;
|
||||
}
|
||||
.btn-domain {
|
||||
margin-top: 20px;
|
||||
}
|
||||
.meta{
|
||||
margin-top: 28px;
|
||||
margin-left: 10px;
|
||||
}
|
||||
</style>
|
@ -1,64 +0,0 @@
|
||||
<template>
|
||||
<pre class="editor" v-el:editor>{{text}}</pre>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
import ace from 'brace'
|
||||
import 'brace/mode/javascript';
|
||||
import 'brace/ext/searchbox';
|
||||
import 'brace/ext/language_tools';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
text: {type: String, required: true},
|
||||
onChange: {type: Function}
|
||||
},
|
||||
data: function(){ return{
|
||||
editor: null,
|
||||
debounced: null,
|
||||
}
|
||||
},
|
||||
methods:{
|
||||
change: function(e){
|
||||
if (!this.debounced){
|
||||
var self = this;
|
||||
this.debounced = _.debounce(
|
||||
function(){
|
||||
if (self.onChange){
|
||||
self.onChange(self.editor.getValue());
|
||||
}
|
||||
},500
|
||||
)
|
||||
}
|
||||
this.debounced();
|
||||
},
|
||||
jump: function(r){
|
||||
this.editor.find(r,{
|
||||
backwards: false,
|
||||
wrap: true,
|
||||
caseSensitive: false,
|
||||
wholeWord: false,
|
||||
regExp: true,
|
||||
});
|
||||
}
|
||||
},
|
||||
ready: function(){
|
||||
var el = this.$els.editor;
|
||||
this.editor = ace.edit(el);
|
||||
this.editor.getSession().setMode('ace/mode/javascript');
|
||||
this.editor.getSession().on('change', this.change);
|
||||
this.editor.setOptions({
|
||||
enableBasicAutocompletion: true
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.editor{
|
||||
width: 100%;
|
||||
height: 500px;
|
||||
}
|
||||
</style>
|
@ -1,41 +0,0 @@
|
||||
<template>
|
||||
<div style="position: relative">
|
||||
<popover v-if="hasMeta()" effect="fade" placement="right" title="Metadata" trigger="hover">
|
||||
<span class="label label-info noselect">Metadata</span>
|
||||
<div slot="content">
|
||||
<table class='table '>
|
||||
<tbody>
|
||||
<tr v-for="(k,v) in meta">
|
||||
<td><b>{{k}}</b></td>
|
||||
<td>{{v}}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</popover>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { popover } from 'vue-strap'
|
||||
|
||||
export default{
|
||||
name: "metadata",
|
||||
data: function(){return{
|
||||
|
||||
};},
|
||||
methods:{
|
||||
hasMeta: function(){
|
||||
return Object.keys(this.meta).length > 0;
|
||||
}
|
||||
},
|
||||
props:{
|
||||
meta:{type:Object, required: true}
|
||||
},
|
||||
components:{popover}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
@ -1,19 +0,0 @@
|
||||
<template>
|
||||
<div class='clearfix'><b class='pull-left'>{{record.type}} {{record.name}} {{record.target | truncate 50}}</b>
|
||||
<div style='margin-left:8px' class='pull-left'><metadata :meta="record.meta"></metadata></div></div>
|
||||
</template>
|
||||
<script>
|
||||
import Metadata from './Metadata'
|
||||
export default{
|
||||
data: function(){return{
|
||||
|
||||
};},
|
||||
props: {
|
||||
record:{type: Object, required:true}
|
||||
},
|
||||
components:{Metadata}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
@ -1,81 +0,0 @@
|
||||
<template>
|
||||
<modal :show.sync="show" :title="query.Domain" width="75%">
|
||||
<div slot='modal-body'><div style='margin:15px;'>
|
||||
<spinner :loading="loading"></spinner>
|
||||
<alert v-if="error" type="danger" >
|
||||
{{error}}
|
||||
</alert>
|
||||
<div v-for="(provider,list) in corrections">
|
||||
<h3>{{provider}} <span v-show="list.length==0" class='glyphicon glyphicon-ok' style='color:rgb(93, 197, 150);'></span></h3>
|
||||
<div v-for="correction in list">
|
||||
<correction :correction="correction"></correction>
|
||||
</div>
|
||||
</div>
|
||||
</div></div>
|
||||
<div slot="modal-footer" class="modal-footer">
|
||||
<button type="button" class="btn btn-default" @click="show = false">Close</button>
|
||||
</div>
|
||||
</modal>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { modal,alert } from 'vue-strap'
|
||||
import spinner from 'vue-spinner/src/ScaleLoader.vue'
|
||||
import Correction from './Correction'
|
||||
export default{
|
||||
props:{
|
||||
show:{
|
||||
required: true,
|
||||
type: Boolean,
|
||||
twoWay: true
|
||||
},
|
||||
query: {type: Object, default:{}},
|
||||
config: Object
|
||||
},
|
||||
data: function(){return{
|
||||
loading: false,
|
||||
error: "",
|
||||
corrections: []
|
||||
};},
|
||||
methods:{
|
||||
preview: function(){
|
||||
var self = this;
|
||||
fetch('/api/preview', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({Config: this.config, Query: this.query})
|
||||
}).then(function(response) {
|
||||
if (response.status != 200){
|
||||
response.text().then(function(txt){
|
||||
self.error = txt;
|
||||
self.loading = false;
|
||||
})
|
||||
return
|
||||
}
|
||||
response.json().then(function(j){
|
||||
self.corrections= j
|
||||
self.loading=false
|
||||
})
|
||||
});
|
||||
}
|
||||
},
|
||||
created: function(){
|
||||
this.$watch('show', function(newVal){
|
||||
if (newVal && !this.loading){
|
||||
this.loading = true;
|
||||
this.error = "";
|
||||
this.corrections = [];
|
||||
this.preview();
|
||||
|
||||
}
|
||||
})
|
||||
},
|
||||
components:{modal,alert,spinner,Correction}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
@ -1,15 +0,0 @@
|
||||
import Vue from 'vue'
|
||||
|
||||
import App from './components/App'
|
||||
|
||||
import truncate from 'vue-truncate'
|
||||
|
||||
Vue.use(truncate);
|
||||
Vue.config.devtools = true;
|
||||
// mount a root Vue instance
|
||||
new Vue({
|
||||
el: 'body',
|
||||
components: {
|
||||
app: App
|
||||
}
|
||||
})
|
57565
web/bundle.js
57565
web/bundle.js
File diff suppressed because one or more lines are too long
@ -1,35 +0,0 @@
|
||||
{
|
||||
"name": "dnscontrol",
|
||||
"version": "1.0.0",
|
||||
"description": "web ide for dnscontrol",
|
||||
"main": "bundle.js",
|
||||
"scripts": {
|
||||
"watch": "webpack -w"
|
||||
},
|
||||
"author": "",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"brace": "^0.8.0",
|
||||
"lodash": "^4.14.1",
|
||||
"vue": "^1.0.26",
|
||||
"vue-multiselect": "^0.3.1",
|
||||
"vue-spinner": "^1.0.2",
|
||||
"vue-strap": "^1.0.11",
|
||||
"vue-truncate": "^1.0.0",
|
||||
"whatwg-fetch": "^1.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"babel-core": "^6.11.4",
|
||||
"babel-loader": "^6.2.4",
|
||||
"babel-plugin-transform-runtime": "^6.12.0",
|
||||
"babel-preset-es2015": "^6.9.0",
|
||||
"babel-runtime": "^6.11.6",
|
||||
"css-loader": "^0.23.1",
|
||||
"strip-sourcemap-loader": "0.0.1",
|
||||
"vue-hot-reload-api": "^2.0.5",
|
||||
"vue-html-loader": "^1.2.3",
|
||||
"vue-loader": "^8.5.3",
|
||||
"vue-style-loader": "^1.0.0",
|
||||
"webpack": "^1.13.1"
|
||||
}
|
||||
}
|
7669
web/static.go
7669
web/static.go
File diff suppressed because it is too large
Load Diff
280
web/web.go
280
web/web.go
@ -1,280 +0,0 @@
|
||||
package web
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
"time"
|
||||
|
||||
"github.com/NYTimes/gziphandler"
|
||||
"github.com/StackExchange/dnscontrol/js"
|
||||
"github.com/StackExchange/dnscontrol/models"
|
||||
"github.com/StackExchange/dnscontrol/normalize"
|
||||
"github.com/StackExchange/dnscontrol/providers"
|
||||
"github.com/StackExchange/dnscontrol/providers/config"
|
||||
)
|
||||
|
||||
var (
|
||||
jsFilename, credsFilename string
|
||||
)
|
||||
|
||||
var apiMux = http.NewServeMux()
|
||||
|
||||
func Serve(jsFile, creds string, devMode bool) {
|
||||
if devMode {
|
||||
runWebpack()
|
||||
}
|
||||
jsFilename, credsFilename = jsFile, creds
|
||||
http.HandleFunc("/helpers.js", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "text/javascript")
|
||||
helpers := js.GetHelpers(devMode)
|
||||
w.Write([]byte(helpers))
|
||||
})
|
||||
|
||||
http.Handle("/bundle.js", gziphandler.GzipHandler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "text/javascript")
|
||||
w.Write(FSMustByte(devMode, "/bundle.js"))
|
||||
})))
|
||||
http.Handle("/api/", apiMux)
|
||||
api("/api/save", save, "POST")
|
||||
api("/api/preview", preview, "POST")
|
||||
api("/api/run", runCorrection, "POST")
|
||||
|
||||
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
err := func() error {
|
||||
script, err := ioutil.ReadFile(jsFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
b64 := base64.StdEncoding.EncodeToString(script)
|
||||
buf := &bytes.Buffer{}
|
||||
err = index.Execute(buf, b64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
w.Write(buf.Bytes())
|
||||
return nil
|
||||
}()
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), 500)
|
||||
}
|
||||
})
|
||||
|
||||
log.Fatal(http.ListenAndServe(":8080", nil))
|
||||
}
|
||||
|
||||
func save(r *http.Request) (interface{}, error) {
|
||||
body, err := ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer r.Body.Close()
|
||||
err = ioutil.WriteFile(jsFilename, body, os.FileMode(0660))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
type correction struct {
|
||||
*models.Correction
|
||||
ID string
|
||||
created time.Time
|
||||
}
|
||||
|
||||
var cache = map[string]*correction{}
|
||||
|
||||
func newCorrection(c *models.Correction) *correction {
|
||||
corr := &correction{
|
||||
Correction: c,
|
||||
created: time.Now(),
|
||||
ID: randomString(),
|
||||
}
|
||||
cache[corr.ID] = corr
|
||||
return corr
|
||||
}
|
||||
|
||||
func randomString() string {
|
||||
buf := make([]byte, 6)
|
||||
rand.Read(buf)
|
||||
return hex.EncodeToString(buf)
|
||||
}
|
||||
|
||||
func runCorrection(r *http.Request) (interface{}, error) {
|
||||
id := r.FormValue("id")
|
||||
c, ok := cache[id]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("Cannot find correction %s.", id)
|
||||
}
|
||||
return nil, c.Correction.F()
|
||||
}
|
||||
|
||||
func preview(r *http.Request) (interface{}, error) {
|
||||
decoder := json.NewDecoder(r.Body)
|
||||
defer r.Body.Close()
|
||||
data := &struct {
|
||||
Config *models.DNSConfig
|
||||
Query struct {
|
||||
Domain string
|
||||
Registrar bool
|
||||
Dsps []bool
|
||||
}
|
||||
}{}
|
||||
if err := decoder.Decode(data); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
//limit to only the domain we care about
|
||||
data.Config.Domains = []*models.DomainConfig{data.Config.FindDomain(data.Query.Domain)}
|
||||
|
||||
errs := normalize.NormalizeAndValidateConfig(data.Config)
|
||||
if len(errs) > 0 {
|
||||
buf := &bytes.Buffer{}
|
||||
for _, err := range errs {
|
||||
fmt.Fprintln(buf, err)
|
||||
}
|
||||
return nil, fmt.Errorf(buf.String())
|
||||
}
|
||||
|
||||
configs, err := config.LoadProviderConfigs(credsFilename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
registrars, err := providers.CreateRegistrars(data.Config, configs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
dsps, err := providers.CreateDsps(data.Config, configs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
domain := data.Config.FindDomain(data.Query.Domain)
|
||||
if domain == nil {
|
||||
return nil, fmt.Errorf("Didn't find domain %s in config.", data.Query.Domain)
|
||||
}
|
||||
|
||||
corrections := map[string][]*correction{}
|
||||
for i, dspName := range domain.Dsps {
|
||||
readOnly := false
|
||||
if i >= len(data.Query.Dsps) || !data.Query.Dsps[i] {
|
||||
if len(domain.Nameservers) == 0 {
|
||||
readOnly = true
|
||||
} else {
|
||||
continue
|
||||
}
|
||||
}
|
||||
dsp, ok := dsps[dspName]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("DSP %s not found", dspName)
|
||||
}
|
||||
dom, err := domain.Copy()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cs := []*correction{}
|
||||
domCorrections, err := dsp.GetDomainCorrections(dom)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(domain.Nameservers) == 0 && len(dom.Nameservers) > 0 {
|
||||
domain.Nameservers = dom.Nameservers
|
||||
}
|
||||
for _, c := range domCorrections {
|
||||
cs = append(cs, newCorrection(c))
|
||||
}
|
||||
if !readOnly {
|
||||
corrections["DSP: "+dspName] = cs
|
||||
}
|
||||
}
|
||||
|
||||
if data.Query.Registrar {
|
||||
reg, ok := registrars[domain.Registrar]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("Registrar %s not found", domain.Registrar)
|
||||
}
|
||||
dom, err := domain.Copy()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cs := []*correction{}
|
||||
regCorrections, err := reg.GetRegistrarCorrections(dom)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, c := range regCorrections {
|
||||
cs = append(cs, newCorrection(c))
|
||||
}
|
||||
corrections["Registrar: "+domain.Registrar] = cs
|
||||
}
|
||||
return corrections, nil
|
||||
}
|
||||
|
||||
var index = template.Must(template.New("index").Parse(`
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>DNSControl IDE</title>
|
||||
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css" integrity="sha384-1q8mTJOASx8j1Au+a5WDVnPi2lkFfwwEAa8hDDdjZlpLegxhjVME1fgjWPGmkzs7" crossorigin="anonymous">
|
||||
</head>
|
||||
<body>
|
||||
<app></app>
|
||||
<script>var initialScript = decodeURIComponent(escape(window.atob("{{.}}")))</script>
|
||||
<script src="./helpers.js" type="text/javascript"></script>
|
||||
<script src="./bundle.js" type="text/javascript"></script>
|
||||
</body>
|
||||
</html>
|
||||
`))
|
||||
|
||||
type apiHandler func(r *http.Request) (interface{}, error)
|
||||
|
||||
func api(route string, f apiHandler, methods ...string) {
|
||||
apiMux.Handle(route, gziphandler.GzipHandler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if len(methods) > 0 {
|
||||
ok := false
|
||||
for _, meth := range methods {
|
||||
if meth == r.Method {
|
||||
ok = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !ok {
|
||||
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
}
|
||||
ret, err := f(r)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), 500)
|
||||
return
|
||||
}
|
||||
if ret == nil {
|
||||
return
|
||||
}
|
||||
m := json.NewEncoder(w)
|
||||
if err = m.Encode(ret); err != nil {
|
||||
http.Error(w, err.Error(), 500)
|
||||
}
|
||||
|
||||
})))
|
||||
}
|
||||
|
||||
func runWebpack() {
|
||||
cmd := exec.Command("npm", "run", "watch")
|
||||
cmd.Dir = "web"
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
go func() { log.Fatal(cmd.Run()) }()
|
||||
}
|
@ -1,37 +0,0 @@
|
||||
var path = require('path')
|
||||
var webpack = require('webpack');
|
||||
|
||||
module.exports = {
|
||||
// entry point of our application
|
||||
entry: './app/main.js',
|
||||
// where to place the compiled bundle
|
||||
output: {
|
||||
path: __dirname,
|
||||
filename: 'bundle.js'
|
||||
},
|
||||
resolve: {
|
||||
extensions: ['', '.js', '.vue'],
|
||||
fallback: [path.join(__dirname, './node_modules')],
|
||||
},
|
||||
resolveLoader: {
|
||||
fallback: [path.join(__dirname, '../node_modules')]
|
||||
},
|
||||
module: {
|
||||
loaders: [
|
||||
{
|
||||
test: /\.js$/,
|
||||
exclude: /(node_modules|bower_components)/,
|
||||
loader: 'babel'
|
||||
},
|
||||
{
|
||||
test: /\.js$/,
|
||||
include: /node_modules/,
|
||||
loader: 'strip-sourcemap-loader'
|
||||
},
|
||||
{
|
||||
test: /\.vue$/, // a regex for matching all files that end in `.vue`
|
||||
loader: 'vue' // loader to use for matched files
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user