1
0
mirror of https://github.com/StackExchange/dnscontrol.git synced 2024-05-11 05:55:12 +00:00

Provide TypeScript typings for dnsconfig.js (#1830)

Co-authored-by: Jeffrey Cafferata <jeffrey@jcid.nl>
Co-authored-by: Tom Limoncelli <tlimoncelli@stackoverflow.com>
This commit is contained in:
Jed Fox
2023-01-12 16:59:42 -05:00
committed by GitHub
parent 34908a76e8
commit 1e337abcdf
70 changed files with 3067 additions and 57 deletions

1
.gitignore vendored
View File

@ -24,5 +24,6 @@ stack.sh
.DS_Store
.vscode/launch.json
.jekyll-cache
/dnscontrol.d.ts
dist/

View File

@ -16,7 +16,7 @@ var goos = flag.String("os", "", "OS to build (linux, windows, or darwin) Defaul
func main() {
flag.Parse()
flags := fmt.Sprintf(`-s -w -X main.SHA="%s" -X main.BuildTime=%d`, getVersion(), time.Now().Unix())
flags := fmt.Sprintf(`-s -w -X "main.SHA=%s" -X main.BuildTime=%d`, getVersion(), time.Now().Unix())
pkg := "github.com/StackExchange/dnscontrol/v3"
build := func(out, goos string) {

28
build/generate/dtsFile.go Normal file
View File

@ -0,0 +1,28 @@
package main
import (
"os"
"strings"
)
func generateDTSFile(funcs string) error {
names := []string{
"base-types",
"fetch",
"others",
}
combined := []string{
"// WARNING: These type definitions are experimental and subject to change in future releases.",
}
for _, name := range names {
content, err := os.ReadFile(join("types", "src", name+".d.ts"))
if err != nil {
return err
}
combined = append(combined, string(content))
}
combined = append(combined, funcs)
os.WriteFile(join("types", "dnscontrol.d.ts"), []byte(strings.Join(combined, "\n\n")), 0644)
return nil
}

View File

@ -0,0 +1,219 @@
package main
import (
"errors"
"fmt"
"os"
"regexp"
"strings"
"golang.org/x/text/cases"
"golang.org/x/text/language"
"gopkg.in/yaml.v3"
)
func join(parts ...string) string {
return strings.Join(parts, string(os.PathSeparator))
}
func fixRuns(s string) string {
lines := strings.Split(s, "\n")
var out []string
for _, line := range lines {
if len(line) == 0 {
if len(out) > 0 && len(out[len(out)-1]) == 0 {
continue
}
}
out = append(out, line)
}
return strings.Join(out, "\n")
}
var delimiterRegex = regexp.MustCompile(`(?m)^---\n`)
func parseFrontMatter(content string) (map[string]interface{}, string, error) {
delimiterIndices := delimiterRegex.FindAllStringIndex(content, 2)
startIndex := delimiterIndices[0][0]
endIndex := delimiterIndices[1][0]
yamlString := content[startIndex+4 : endIndex]
var frontMatter map[string]interface{}
err := yaml.Unmarshal([]byte(yamlString), &frontMatter)
if err != nil {
return nil, "", err
}
return frontMatter, content[endIndex+4:], nil
}
var returnTypes = map[string]string{
"domain": "DomainModifier",
"global": "void",
"record": "RecordModifier",
}
func generateFunctionTypes() (string, error) {
funcs := []Function{}
srcRoot := join("docs", "_functions")
types, err := os.ReadDir(srcRoot)
if err != nil {
return "", err
}
for _, t := range types {
if !t.IsDir() {
return "", errors.New("not a directory: " + join(srcRoot, t.Name()))
}
tPath := join(srcRoot, t.Name())
funcNames, err := os.ReadDir(tPath)
if err != nil {
return "", err
}
for _, f := range funcNames {
fPath := join(tPath, f.Name())
if f.IsDir() {
return "", errors.New("not a file: " + fPath)
}
// println("Processing", fPath)
content, err := os.ReadFile(fPath)
if err != nil {
return "", err
}
frontMatter, body, err := parseFrontMatter(string(content))
if err != nil {
println("Error parsing front matter in", fPath)
return "", err
}
if frontMatter["ts_ignore"] == true {
continue
}
body = body + "\n"
body = strings.ReplaceAll(body, "{{site.github.url}}", "https://dnscontrol.org/")
body = strings.ReplaceAll(body, "{% capture example %}", "")
body = strings.ReplaceAll(body, "{% capture example2 %}", "")
body = strings.ReplaceAll(body, "{% endcapture %}", "")
body = strings.ReplaceAll(body, "{% include example.html content=example %}", "")
body = strings.ReplaceAll(body, "{% include example.html content=example2 %}", "")
body = strings.ReplaceAll(body, "](#", "](https://dnscontrol.org/js#")
body = fixRuns(body)
paramNames := []string{}
if frontMatter["parameters"] != nil {
for _, p := range frontMatter["parameters"].([]interface{}) {
paramNames = append(paramNames, p.(string))
}
}
suppliedParamTypes := map[string]string{}
if frontMatter["parameter_types"] != nil {
rawTypes := frontMatter["parameter_types"].(map[string]interface{})
for k, v := range rawTypes {
suppliedParamTypes[k] = v.(string)
}
}
params := []Param{}
for _, p := range paramNames {
// start with supplied type, fall back to defaultParamType
paramType := suppliedParamTypes[p]
if paramType == "" {
println("WARNING:", fPath+":", "no type for parameter ", "'"+p+"'")
paramType = "unknown"
}
params = append(params, Param{Name: p, Type: paramType})
}
returnType := returnTypes[t.Name()]
if frontMatter["ts_return"] != nil {
returnType = frontMatter["ts_return"].(string)
} else if frontMatter["return"] != nil {
returnType = frontMatter["return"].(string)
}
if len(params) == 0 {
if frontMatter["ts_is_function"] != true {
params = nil
}
}
funcs = append(funcs, Function{
Name: frontMatter["name"].(string),
Params: params,
ObjectParam: frontMatter["parameters_object"] == true,
Deprecated: frontMatter["deprecated"] == true,
ReturnType: returnType,
Description: strings.TrimSpace(body),
})
}
}
content := ""
for _, f := range funcs {
content += f.String()
}
return content, nil
}
type Function struct {
Name string
Params []Param
ObjectParam bool
Deprecated bool
ReturnType string
Description string
}
type Param struct {
Name string
Type string
}
var caser = cases.Title(language.AmericanEnglish)
func (f Function) formatParams() string {
var params []string
for _, p := range f.Params {
name := p.Name
if strings.HasSuffix(name, "...") {
name = "..." + name[:len(name)-3]
}
if strings.Contains(name, " ") {
name = strings.ReplaceAll(caser.String(name), " ", "")
name = strings.ToLower(name[:1]) + name[1:]
}
typeName := p.Type
if strings.HasSuffix(typeName, "?") {
typeName = typeName[:len(typeName)-1]
name += "?"
}
params = append(params, fmt.Sprintf("%s: %s", name, typeName))
}
if f.ObjectParam {
return "opts: { " + strings.Join(params, "; ") + " }"
} else {
return strings.Join(params, ", ")
}
}
func (f Function) docs() string {
content := f.Description
if f.Deprecated {
content += "\n\n@deprecated"
}
content += "\n\n@see https://dnscontrol.org/js#" + f.Name
return "/**\n * " + strings.ReplaceAll(content, "\n", "\n * ") + "\n */"
}
func (f Function) formatMain() string {
if f.Params == nil {
return fmt.Sprintf("declare const %s: %s", f.Name, f.ReturnType)
}
return fmt.Sprintf("declare function %s(%s): %s", f.Name, f.formatParams(), f.ReturnType)
}
func (f Function) String() string {
return fmt.Sprintf("%s\n%s;\n\n", f.docs(), f.formatMain())
}

View File

@ -6,4 +6,11 @@ func main() {
if err := generateFeatureMatrix(); err != nil {
log.Fatal(err)
}
funcs, err := generateFunctionTypes()
if err != nil {
log.Fatal(err)
}
if err := generateDTSFile(funcs); err != nil {
log.Fatal(err)
}
}

70
commands/writeTypes.go Normal file
View File

@ -0,0 +1,70 @@
package commands
import (
"io"
"net/http"
"os"
"strings"
versionInfo "github.com/StackExchange/dnscontrol/v3/pkg/version"
"github.com/urfave/cli/v2"
)
var _ = cmd(catUtils, func() *cli.Command {
var args TypesArgs
return &cli.Command{
Name: "write-types",
Usage: "[BETA] Write a TypeScript declaration file in the current directory",
Action: func(c *cli.Context) error {
return exit(WriteTypes(args))
},
Flags: args.flags(),
}
}())
// TypesArgs stores arguments related to the types subcommand.
type TypesArgs struct {
DTSFile string
}
func (args *TypesArgs) flags() []cli.Flag {
var flags []cli.Flag
flags = append(flags, &cli.StringFlag{
Name: "dts-file",
Aliases: []string{"o"},
Value: "types-dnscontrol.d.ts",
Usage: "Path to the .d.ts file to create",
Destination: &args.DTSFile,
})
return flags
}
func WriteTypes(args TypesArgs) error {
url := "https://raw.githubusercontent.com/StackExchange/dnscontrol/" + strings.Replace(versionInfo.SHA, "[dirty]", "", 1) + "/types/dnscontrol.d.ts"
print("Downloading " + url + " to " + args.DTSFile + "...")
defer print("\n")
resp, err := http.Get(url)
if err != nil {
return err
}
defer resp.Body.Close()
file, err := os.Create(args.DTSFile)
if err != nil {
return err
}
defer file.Close()
file.WriteString("// This file was automatically generated by DNSControl. Do not edit it directly.\n")
file.WriteString("// To update it, run `dnscontrol write-types`.\n\n")
file.WriteString("// DNSControl version: " + versionInfo.Banner() + "\n")
file.WriteString("// Source: " + url + "\n\n")
_, err = io.Copy(file, resp.Body)
if err != nil {
return err
}
print(" done.")
return nil
}

View File

@ -4,6 +4,10 @@ parameters:
- name
- address
- modifiers...
parameter_types:
name: string
address: string | number
"modifiers...": RecordModifier[]
---
A adds an A record To a domain. The name should be the relative label for the record. Use `@` for the domain apex.

View File

@ -4,6 +4,10 @@ parameters:
- name
- address
- modifiers...
parameter_types:
name: string
address: string
"modifiers...": RecordModifier[]
---
AAAA adds an AAAA record To a domain. The name should be the relative label for the record. Use `@` for the domain apex.

View File

@ -5,6 +5,10 @@ parameters:
- target
- modifiers...
provider: AKAMAIEDGEDNS
parameter_types:
name: string
target: string
"modifiers...": RecordModifier[]
---
AKAMAICDN is a proprietary record type that is used to configure [Zone Apex Mapping](https://blogs.akamai.com/2019/08/fast-dns-zone-apex-mapping-dnssec.html).

View File

@ -4,6 +4,10 @@ parameters:
- name
- target
- modifiers...
parameter_types:
name: string
target: string
"modifiers...": RecordModifier[]
---
ALIAS is a virtual record type that points a record at another record. It is analogous to a CNAME, but is usually resolved at request-time and served as an A record. Unlike CNAMEs, ALIAS records can be used at the zone apex (`@`)

View File

@ -4,7 +4,12 @@ parameters:
- name
- type
- target
- modifiers ...
- modifiers...
parameter_types:
name: string
type: '"A" | "AAAA" | "CNAME"'
target: string
"modifiers...": RecordModifier[]
provider: AZURE_DNS
---

View File

@ -5,6 +5,11 @@ parameters:
- tag
- value
- modifiers...
parameter_types:
name: string
tag: '"issue" | "issuewild" | "iodef"'
value: string
"modifiers...": RecordModifier[]
---
CAA adds a CAA record to a domain. The name should be the relative label for the record. Use `@` for the domain apex.

View File

@ -1,9 +1,14 @@
---
name: CF_REDIRECT
parameters:
- source
- destination
- modifiers...
provider: CLOUDFLAREAPI
parameter_types:
source: string
destination: string
"modifiers...": RecordModifier[]
---
`CF_REDIRECT` uses Cloudflare-specific features ("Forwarding URL" Page Rules) to

View File

@ -1,9 +1,14 @@
---
name: CF_TEMP_REDIRECT
parameters:
- source
- destination
- modifiers...
provider: CLOUDFLAREAPI
parameter_types:
source: string
destination: string
"modifiers...": RecordModifier[]
---
`CF_TEMP_REDIRECT` uses Cloudflare-specific features ("Forwarding URL" Page

View File

@ -3,6 +3,9 @@ name: CF_WORKER_ROUTE
parameters:
- pattern
- script
parameter_types:
pattern: string
script: string
provider: CLOUDFLAREAPI
---

View File

@ -0,0 +1,13 @@
---
name: CLOUDNS_WR
parameters:
- name
- target
- modifiers...
parameter_types:
name: string
target: string
"modifiers...": RecordModifier[]
---
Documentation needed.

View File

@ -4,6 +4,10 @@ parameters:
- name
- target
- modifiers...
parameter_types:
name: string
target: string
"modifiers...": RecordModifier[]
---
CNAME adds a CNAME record to the domain. The name should be the relative label for the domain.

View File

@ -7,6 +7,13 @@ parameters:
- digesttype
- digest
- modifiers...
parameter_types:
name: string
keytag: number
algorithm: number
digesttype: number
digest: string
"modifiers...": RecordModifier[]
---
DS adds a DS record to the domain.

View File

@ -2,6 +2,8 @@
name: DefaultTTL
parameters:
- ttl
parameter_types:
ttl: Duration
---
DefaultTTL sets the TTL for all records in a domain that do not explicitly set one with [TTL](#TTL). If neither `DefaultTTL` or `TTL` exist for a record,

View File

@ -3,6 +3,9 @@ name: DnsProvider
parameters:
- name
- nsCount
parameter_types:
name: string
nsCount: number?
---
DnsProvider indicates that the specified provider should be used to manage

View File

@ -4,6 +4,10 @@ parameters:
- name
- target
- modifiers...
parameter_types:
name: string
target: string
"modifiers...": RecordModifier[]
---
Documentation needed.

View File

@ -1,5 +1,6 @@
---
name: IGNORE
ts_ignore: true
---
IGNORE has been renamed to `IGNORE_NAME`. IGNORE will continue to function, but its use is deprecated. Please update your configuration files to use `IGNORE_NAME`.

View File

@ -3,6 +3,9 @@ name: IGNORE_NAME
parameters:
- pattern
- rTypes
parameter_types:
pattern: string
rTypes: string?
---
WARNING: The `IGNORE_*` family of functions is risky to use. The code

View File

@ -3,6 +3,9 @@ name: IGNORE_TARGET
parameters:
- pattern
- rType
parameter_types:
pattern: string
rType: string
---
WARNING: The `IGNORE_*` family of functions is risky to use. The code

View File

@ -4,6 +4,7 @@ parameters:
- transform table
- domain
- modifiers...
ts_ignore: true
---
Don't use this feature. It was added for a very specific situation at Stack Overflow.

View File

@ -2,6 +2,8 @@
name: INCLUDE
parameters:
- domain
parameter_types:
domain: string
---
Includes all records from a given domain

View File

@ -5,6 +5,11 @@ parameters:
- priority
- target
- modifiers...
parameter_types:
name: string
priority: number
target: string
"modifiers...": RecordModifier[]
---
MX adds an MX record to the domain.

View File

@ -3,6 +3,9 @@ name: NAMESERVER
parameters:
- name
- modifiers...
parameter_types:
name: string
"modifiers...": RecordModifier[]
---
`NAMESERVER()` instructs DNSControl to inform the domain's registrar where to find this zone.

View File

@ -2,6 +2,10 @@
name: NAMESERVER_TTL
parameters:
- ttl
parameter_types:
ttl: Duration
target: string
modifiers...: RecordModifier[]
---
TTL sets the TTL on the domain apex NS RRs defined by [NAMESERVER](#NAMESERVER).

View File

@ -4,6 +4,10 @@ parameters:
- name
- target
- modifiers...
parameter_types:
name: string
target: string
"modifiers...": RecordModifier[]
---
NS adds a NS record to the domain. The name should be the relative label for the domain.

View File

@ -0,0 +1,13 @@
---
name: NS1_URLFWD
parameters:
- name
- target
- modifiers...
parameter_types:
name: string
target: string
"modifiers...": RecordModifier[]
---
Documentation needed.

View File

@ -4,6 +4,10 @@ parameters:
- name
- target
- modifiers...
parameter_types:
name: string
target: string
"modifiers...": RecordModifier[]
---
PTR adds a PTR record to the domain.

View File

@ -4,6 +4,10 @@ parameters:
- name
- target
- ZONE_ID modifier
parameter_types:
name: string
target: string
ZONE_ID modifier: DomainModifier & RecordModifier
provider: ROUTE53
---

View File

@ -9,6 +9,15 @@ parameters:
- expire
- minttl
- modifiers...
parameter_types:
name: string
ns: string
mbox: string
refresh: number
retry: number
expire: number
minttl: number
"modifiers...": RecordModifier[]
---
`SOA` adds an `SOA` record to a domain. The name should be `@`. ns and mbox are strings. The other fields are unsigned 32-bit ints.

View File

@ -7,6 +7,13 @@ parameters:
- port
- target
- modifiers...
parameter_types:
name: string
priority: number
weight: number
port: number
target: string
"modifiers...": RecordModifier[]
---
`SRV` adds a `SRV` record to a domain. The name should be the relative label for the record.

View File

@ -6,6 +6,12 @@ parameters:
- type
- value
- modifiers...
parameter_types:
name: string
algorithm: 0 | 1 | 2 | 3 | 4
type: 0 | 1 | 2
value: string
"modifiers...": RecordModifier[]
---
SSHFP contains a fingerprint of a SSH server which can be validated before SSH clients are establishing the connection.

View File

@ -7,6 +7,13 @@ parameters:
- type
- certificate
- modifiers...
parameter_types:
name: string
usage: number
selector: number
type: number
certificate: string
"modifiers...": RecordModifier[]
---
TLSA adds a TLSA record to a domain. The name should be the relative label for the record.

View File

@ -4,6 +4,10 @@ parameters:
- name
- contents
- modifiers...
parameter_types:
name: string
contents: string
"modifiers...": RecordModifier[]
---
TXT adds an TXT record To a domain. The name should be the relative

View File

@ -2,7 +2,12 @@
name: URL
parameters:
- name
- target
- modifiers...
parameter_types:
name: string
target: string
"modifiers...": RecordModifier[]
---
Documentation needed.

View File

@ -3,6 +3,10 @@ name: URL301
parameters:
- name
- modifiers...
parameter_types:
name: string
target: string
"modifiers...": RecordModifier[]
---
Documentation needed.

View File

@ -4,6 +4,10 @@ parameters:
- name
- registrar
- modifiers...
parameter_types:
name: string
registrar: string
"modifiers...": DomainModifier[]
---
`D` adds a new Domain for DNSControl to manage. The first two arguments are required: the domain name (fully qualified `example.com` without a trailing dot), and the

View File

@ -2,6 +2,8 @@
name: DEFAULTS
parameters:
- modifiers...
parameter_types:
"modifiers...": RecordModifier[]
---
`DEFAULTS` allows you to declare a set of default arguments to apply to all subsequent domains. Subsequent calls to [D](#D) will have these

View File

@ -2,7 +2,10 @@
name: DOMAIN_ELSEWHERE
parameters:
- registrar
- list of nameserver names
- nameserver_names
parameter_types:
registrar: string
nameserver_names: string[]
---
`DOMAIN_ELSEWHERE()` is a helper macro that lets you easily indicate that

View File

@ -1,8 +1,13 @@
---
name: DOMAIN_ELSEWHERE_AUTO
parameters:
- domain
- registrar
- list of Dns Providers
- dns provider
parameter_types:
domain: string
registrar: string
dns provider: string
---
`DOMAIN_ELSEWHERE_AUTO()` is similar to `DOMAIN_ELSEWHERE()` but instead of

View File

@ -3,6 +3,9 @@ name: D_EXTEND
parameters:
- name
- modifiers...
parameter_types:
name: string
"modifiers...": DomainModifier[]
---
`D_EXTEND` adds records (and metadata) to a domain previously defined

View File

@ -3,6 +3,8 @@ name: FETCH
parameters:
- url
- args
ts_ignore: true
# Make sure to update fetch.d.ts if changing the docs below!
---
`FETCH` is a wrapper for the [Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API). This allows dynamically setting DNS records based on an external data source, e.g. the API of your cloud provider.

View File

@ -2,6 +2,9 @@
name: IP
parameters:
- ip
parameter_types:
ip: string
return: number
---
Converts an IPv4 address from string to an integer. This allows performing mathematical operations with the IP address.

View File

@ -4,6 +4,10 @@ parameters:
- name
- type
- meta
parameter_types:
name: string
type: string?
meta: object?
return: string
---

View File

@ -4,6 +4,10 @@ parameters:
- name
- type
- meta
parameter_types:
name: string
type: string?
meta: object?
return: string
---

View File

@ -2,6 +2,9 @@
name: PANIC
parameters:
- message
parameter_types:
message: string
ts_return: never
---
`PANIC` terminates the script and therefore DNSControl with an exit code of 1. This should be used if your script cannot gather enough information to generate records, for example when a HTTP request failed.

View File

@ -2,6 +2,9 @@
name: REV
parameters:
- address
parameter_types:
address: string
ts_return: string
---
`REV` returns the reverse lookup domain for an IP network. For

View File

@ -1,8 +1,7 @@
---
name: getConfiguredDomains
parameters:
- name
- modifiers...
ts_is_function: true
ts_return: string[]
---
`getConfiguredDomains` getConfiguredDomains is a helper function that returns the domain names

View File

@ -2,6 +2,7 @@
name: require
parameters:
- path
ts_ignore: true
---
`require(...)` loads the specified JavaScript or JSON file, allowing
@ -41,14 +42,18 @@ function IncludeKubernetes() {
```js
// kubernetes/clusters/prod.js
function includeK8Sprod() {
return [ /* ... */ ];
return [
// ...
];
}
```
```js
// kubernetes/clusters/dev.js
function includeK8Sdev() {
return [ /* ... */ ];
return [
// ...
];
}
```
{% endcapture %}

View File

@ -3,6 +3,9 @@ name: require_glob
parameters:
- path
- recursive
parameter_types:
path: string
recursive: boolean
---
`require_glob()` can recursively load `.js` files, optionally non-recursive as well.

View File

@ -6,10 +6,15 @@ parameters:
- iodef_critical
- issue
- issuewild
parameters_object: true
parameter_types:
label: string?
iodef: string
iodef_critical: boolean?
issue: string[]
issuewild: string
---
# CAA Builder
DNSControl contains a `CAA_BUILDER` which can be used to simply create
CAA records for your domains. Instead of creating each CAA record
individually, you can simply configure your report mail address, the

View File

@ -14,10 +14,23 @@ parameters:
- failureFormat
- reportInterval
- ttl
parameters_object: true
parameter_types:
label: string?
version: string?
policy: "'none' | 'quarantine' | 'reject'"
subdomainPolicy: "'none' | 'quarantine' | 'reject'?"
alignmentSPF: "'strict' | 's' | 'relaxed' | 'r'?"
alignmentDKIM: "'strict' | 's' | 'relaxed' | 'r'?"
percent: number?
rua: string[]?
ruf: string[]?
failureOptions: "{ SPF: boolean, DKIM: boolean } | string?"
failureFormat: string?
reportInterval: Duration?
ttl: Duration?
---
# DMARC Builder
DNSControl contains a `DMARC_BUILDER` which can be used to simply create
DMARC policies for your domains.

View File

@ -2,6 +2,9 @@
name: R53_ZONE
parameters:
- zone_id
parameter_types:
zone_id: string
ts_return: DomainModifier & RecordModifier
provider: ROUTE53
---

View File

@ -9,6 +9,16 @@ parameters:
- txtMaxSize
- parts
- flatten
parameters_object: true
parameter_types:
label: string?
overflow: string?
overhead1: string?
raw: string?
ttl: Duration?
txtMaxSize: string[]
parts: number?
flatten: string[]?
---
# SPF Optimizer

View File

@ -2,6 +2,8 @@
name: TTL
parameters:
- ttl
parameter_types:
ttl: Duration
---
TTL sets the TTL for a single record only. This will take precedence

View File

@ -17,9 +17,9 @@ to do.
Our general philosophy is:
* Internally the individual fields of a record are kept separate. If a particular provider combines them into one big string, that kind of thing is done in the provider code at the end of the food chain. For example, an MX record has a Target (`aspmx.l.google.com.`) and a preference (`10`). Some systems combine this into one string (`10 aspmx.l.google.com.`). We keep the two values separate in `RecordConfig` and leave it up to the individual providers to merge them when required. An earlier implementation kept everything combined and we found ourselves constantly parsing and re-parsing the target. It was inefficient and lead to many bugs.
* Anywhere we have a special case for a particular Rtype, we use a `switch` statement and have a `case` for every single record type, usually with a `default:` case that calls `panic()`. This way developers adding a new record type will quickly find where they need to add code (the panic will tell them where). Before we did this, missing implementation code would go unnoticed for months.
* Keep things alphabetical. If you are adding your record type to a case statement, function library, or whatever, please list it alphabetically along with the others when possible.
- Internally the individual fields of a record are kept separate. If a particular provider combines them into one big string, that kind of thing is done in the provider code at the end of the food chain. For example, an MX record has a Target (`aspmx.l.google.com.`) and a preference (`10`). Some systems combine this into one string (`10 aspmx.l.google.com.`). We keep the two values separate in `RecordConfig` and leave it up to the individual providers to merge them when required. An earlier implementation kept everything combined and we found ourselves constantly parsing and re-parsing the target. It was inefficient and lead to many bugs.
- Anywhere we have a special case for a particular Rtype, we use a `switch` statement and have a `case` for every single record type, usually with a `default:` case that calls `panic()`. This way developers adding a new record type will quickly find where they need to add code (the panic will tell them where). Before we did this, missing implementation code would go unnoticed for months.
- Keep things alphabetical. If you are adding your record type to a case statement, function library, or whatever, please list it alphabetically along with the others when possible.
## Step 1: Update `RecordConfig` in `models/dns.go`
@ -50,22 +50,22 @@ You'll need to mark which providers support this record type. The
initial PR should implement this record for the `bind` provider at
a minimum.
* Add the capability to the file `dnscontrol/providers/capabilities.go` (look for `CanUseAlias` and add
it to the end of the list.)
* Add this feature to the feature matrix in `dnscontrol/build/generate/featureMatrix.go` (Add it to the variable `matrix` then add it later in the file with a `setCap()` statement.
* Add the capability to the list of features that zones are validated
- Add the capability to the file `dnscontrol/providers/capabilities.go` (look for `CanUseAlias` and add
it to the end of the list.)
- Add this feature to the feature matrix in `dnscontrol/build/generate/featureMatrix.go` (Add it to the variable `matrix` then add it later in the file with a `setCap()` statement.
- Add the capability to the list of features that zones are validated
against (i.e. if you want DNSControl to report an error if this
feature is used with a DNS provider that doesn't support it). That's
in the `checkProviderCapabilities` function in
`pkg/normalize/validate.go`.
* Mark the `bind` provider as supporting this record type by updating `dnscontrol/providers/bind/bindProvider.go` (look for `providers.CanUse` and you'll see what to do).
- Mark the `bind` provider as supporting this record type by updating `dnscontrol/providers/bind/bindProvider.go` (look for `providers.CanUse` and you'll see what to do).
DNSControl will warn/error if this new record is used with a
provider that does not support the capability.
* Add the capability to the validations in `pkg/normalize/validate.go`
- Add the capability to the validations in `pkg/normalize/validate.go`
by adding it to `providerCapabilityChecks`
* Some capabilities can't be tested for. If
- Some capabilities can't be tested for. If
such testing can't be done, add it to the whitelist in function
`TestCapabilitiesAreFiltered` in
`pkg/normalize/capabilities_test.go`
@ -220,3 +220,13 @@ please add a test that demonstrates the bug (then fix the bug, of
course). This
will help all future contributors. If you need help with adding
tests, please ask!
## Step 8: Write documentation
Add a new Markdown file to `docs/_functions/domain`. Copy an existing file (`CNAME.md` is a good example). The section between the lines of `---` is called the front matter and it has the following keys:
- `name`: The name of the record. This should match the file name and the name of the record in `helpers.js`.
- `parameters`: A list of parameter names, in order. Feel free to use spaces in the name if necessary. Your last parameter should be `modifiers...` to allow arbitrary modifiers like `TTL` to be applied to your record.
- `parameter_types`: an object with parameter names as keys and TypeScript type names as values. Check out existing record documentation if youre not sure to put for a parameter. Note that this isnt displayed on the website, its only used to generate the `.d.ts` file.
The rest of the file is the documentation. You can use Markdown syntax to format the text. Use the `{% capture example %}` format to set up a collapsible code block. You can also use it multiple times to show multiple examples.

View File

@ -23,7 +23,7 @@ Alternatively on macOS you can install it using [MacPorts](https://www.macports.
```bash
sudo port install dnscontrol
````
```
### Docker

View File

@ -93,6 +93,9 @@ title: DNSControl
<li>
<a href="{{site.github.url}}/migrating">Migrating</a>: Migrating zones to DNSControl
</li>
<li>
<a href="{{site.github.url}}/typescript">TypeScript</a> (optional): Improve autocomplete and add type checking
</li>
</ul>
<h2 id="commands">

View File

@ -12,10 +12,24 @@ CircleCI will do most of the work for you. You will need to edit the draft relea
Please change the version number as appropriate. Substitute (for example)
`3.20.0` any place you see `VERSION` in this doc.
## Step 1. Tag the commit in master that you want to release
## Step 1. Rebuild generated files
```shell
git checkout master
git pull
go generate
git add -A
git commit -m "Update generated files for VERSION"
```
This will update the following generated files:
* `docs/_includes/matrix.md`
* `types/dnscontrol.d.ts`
## Step 2. Tag the commit in master that you want to release
```shell
git tag -a v3.20.0
git push origin --tags
```
@ -27,7 +41,7 @@ CircleCI will start a [build](https://app.circleci.com/pipelines/github/StackExc
![CircleCI Release Screenshot](public/circleci_release.png)
## Step 2. Create the release notes
## Step 3. Create the release notes
The draft release notes are created for you. In this step you'll edit them.
@ -73,7 +87,7 @@ of bug fixes, and FILL IN.
### Deprecation warnings
```
## Step 2. Announce it via email
## Step 4. Announce it via email
Email the release notes to the mailing list: (note the format of the Subject line and that the first line of the email is the URL of the release)
@ -89,7 +103,7 @@ https://github.com/StackExchange/dnscontrol/releases/tag/v$VERSION
NOTE: You won't be able to post to the mailing list unless you are on
it. [Click here to join](https://groups.google.com/forum/#!forum/dnscontrol-discuss).
## Step 3. Announce it via chat
## Step 5. Announce it via chat
Mention on [https://gitter.im/dnscontrol/Lobby](https://gitter.im/dnscontrol/Lobby) that the new release has shipped.
@ -97,7 +111,7 @@ Mention on [https://gitter.im/dnscontrol/Lobby](https://gitter.im/dnscontrol/Lob
ANNOUNCEMENT: dnscontrol v$VERSION has been released! https://github.com/StackExchange/dnscontrol/releases/tag/v$VERSION
```
## Step 4. Get credit
## Step 6. Get credit
Mention the fact that you did this release in your weekly accomplishments.

29
docs/typescript.md Normal file
View File

@ -0,0 +1,29 @@
---
layout: default
title: '[EXPERIMENTAL] Using TypeScript with DNSControl'
---
# [EXPERIMENTAL] Using TypeScript with DNSControl
> **NOTE**: This feature is currently experimental. Type information may change in future releases, and any release (even a patch release) could introduce new type-checking errors into your `dnsconfig.js` file. If you plan to use TypeScript, please file bug reports for any issues you encounter, and avoid manually running type-checking as part of your deployment process until the “experimental” label is removed.
While DNSControl does not support TypeScript syntax in `dnsconfig.js`, you can still use TypeScripts features in editors which support it (including Visual Studio Code). To set up TypeScript support in Visual Studio Code, follow these steps:
First, youll need to grab the `dnscontrol.d.ts` file corresponding to your version of DNSControl. You can manually download this file off of GitHub, or you can use the following command:
```bash
dnscontrol write-types
```
When run, `dnscontrol write-types` will create a `dnscontrol.d.ts` file in the current directory, overwriting an existing file if it exists. The file will use the type information corresponding to the current version of `dnscontrol`, so you can be confident that everything in the type declarations are consistent with DNSControl functionality. That does mean that you should re-run `dnscontrol write-types` when you update DNSControl, though!
That should be all you need to do! If youre using VS Code (or another editor that supports TypeScript), you should now be able to see the type information in your `dnsconfig.js` file as you type. Hover over record names to read their documentation without having to open the website!
## Type Checking
If you add the comment `// @ts-check` to the top of your `dnsconfig.js` file, you can enable _type checking_ for your DNSControl configuration. This will allow your editors integrated version of TypeScript to check your configuration for possible mistakes in addition to providing enhanced autocomplete. Note that not all features of DNSControl work perfectly at the moment.
Specifically:
- Values passed to `CLI_DEFAULTS` (and the corresponding `-v` command-line option) dont show up as global variables
- Workaround: create a new `.d.ts` file in the same folder as your `dnsconfig.js` file. In that file, add the following line: <code>declare const _variableName_: string;</code> for each variable you want to use. This will tell TypeScript that the variable exists, and that its a string.
- `FETCH` is always shown as available, even if you dont run DNSControl with the `--allow-fetch` flag.

View File

@ -11,7 +11,7 @@ import (
_ "github.com/StackExchange/dnscontrol/v3/providers/_all"
)
//go:generate go run build/generate/generate.go build/generate/featureMatrix.go
//go:generate go run build/generate/generate.go build/generate/featureMatrix.go build/generate/functionTypes.go build/generate/dtsFile.go
// Version management. Goals:
// 1. Someone who just does "go get" has at least some information.

View File

@ -41,11 +41,11 @@ type differCompat struct {
// IncrementalDiff generates the diff using the pkg/diff2 code.
// NOTE: While this attempts to be backwards compatible, it does not
// support all features of the old system:
// * The IncrementalDiff() `unchanged` return value is always empty.
// - The IncrementalDiff() `unchanged` return value is always empty.
// Most providers ignore this return value. If a provider depends on
// that result, please consider one of the pkg/diff2/By*() functions
// instead. (ByZone() is likely to be what you need)
// * The NewCompat() feature `extraValues` is not supported. That
// - The NewCompat() feature `extraValues` is not supported. That
// parameter must be set to nil. If you use that feature, consider
// one of the pkg/diff2/By*() functions.
func (d *differCompat) IncrementalDiff(existing []*models.RecordConfig) (unchanged, create, toDelete, modify Changeset, err error) {

2219
types/dnscontrol.d.ts vendored Normal file

File diff suppressed because it is too large Load Diff

32
types/src/base-types.d.ts vendored Normal file
View File

@ -0,0 +1,32 @@
interface Domain {
name: string;
subdomain: string;
registrar: unknown;
meta: Record<string, unknown>;
records: DNSRecord[];
dnsProviders: Record<string, unknown>;
defaultTTL: number;
nameservers: unknown[];
ignored_names: unknown[];
ignored_targets: unknown[];
[key: string]: unknown;
}
interface DNSRecord {
type: string;
meta: Record<string, unknown>;
ttl: number;
}
type DomainModifier =
| ((domain: Domain) => void)
| Partial<Domain>
| DomainModifier[];
type RecordModifier =
| ((record: DNSRecord) => void)
| Partial<DNSRecord['meta']>;
type Duration =
| `${number}${'s' | 'm' | 'h' | 'd' | 'w' | 'n' | 'y' | ''}`
| number /* seconds */;

76
types/src/fetch.d.ts vendored Normal file
View File

@ -0,0 +1,76 @@
/**
* `FETCH` is a wrapper for the [Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API). This allows dynamically setting DNS records based on an external data source, e.g. the API of your cloud provider.
*
* Compared to `fetch` from Fetch API, `FETCH` will call [PANIC](https://dnscontrol.org/js#PANIC) to terminate the execution of the script, and therefore DNSControl, if a network error occurs.
*
* Otherwise the syntax of `FETCH` is the same as `fetch`.
*
* `FETCH` is not enabled by default. Please read the warnings below.
*
* > WARNING:
* >
* > 1. Relying on external sources adds a point of failure. If the external source doesn't work, your script won't either. Please make sure you are aware of the consequences.
* > 2. Make sure DNSControl only uses verified configuration if you want to use `FETCH`. For example, an attacker can send Pull Requests to your config repo, and have your CI test malicious configurations and make arbitrary HTTP requests. Therefore, `FETCH` must be explicitly enabled with flag `--allow-fetch` on DNSControl invocation.
*
* ```js
* var REG_NONE = NewRegistrar('none');
* var DNS_BIND = NewDnsProvider('bind');
*
* D('example.com', REG_NONE, DnsProvider(DNS_BIND), [
* A('@', '1.2.3.4'),
* ]);
*
* FETCH('https://example.com', {
* // All three options below are optional
* headers: {"X-Authentication": "barfoo"},
* method: "POST",
* body: "Hello World",
* }).then(function(r) {
* return r.text();
* }).then(function(t) {
* // Example of generating record based on response
* D_EXTEND('example.com', [
* TXT('@', t.slice(0, 100)),
* ]);
* });
* ```
*/
declare function FETCH(
url: string,
init?: {
method?:
| 'GET'
| 'POST'
| 'PUT'
| 'PATCH'
| 'DELETE'
| 'HEAD'
| 'OPTIONS';
headers?: { [key: string]: string | string[] };
// Ignored by the underlying code
// redirect: 'follow' | 'error' | 'manual';
body?: string;
}
): Promise<FetchResponse>;
interface FetchResponse {
readonly bodyUsed: boolean;
readonly headers: ResponseHeaders;
readonly ok: boolean;
readonly status: number;
readonly statusText: string;
readonly type: string;
text(): Promise<string>;
json(): Promise<any>;
}
interface ResponseHeaders {
get(name: string): string | undefined;
getAll(name: string): string[];
has(name: string): boolean;
append(name: string, value: string): void;
delete(name: string): void;
set(name: string, value: string): void;
}

65
types/src/others.d.ts vendored Normal file
View File

@ -0,0 +1,65 @@
declare function require(name: `${string}.json`): any;
declare function require(name: string): true;
/**
* Issuer critical flag. CA that does not understand this tag will refuse to issue certificate for this domain.
*
* CAA record is supported only by BIND, Google Cloud DNS, Amazon Route 53 and OVH. Some certificate authorities may not support this record until the mandatory date of September 2017.
*/
declare const CAA_CRITICAL: RecordModifier;
/**
* This disables a safety check intended to prevent:
* 1. Two owners toggling a record between two settings.
* 2. The other owner wiping all records at this label, which won't
* be noticed until the next time dnscontrol is run.
* See https://github.com/StackExchange/dnscontrol/issues/1106
*/
declare const IGNORE_NAME_DISABLE_SAFETY_CHECK: RecordModifier;
// Cloudflare aliases:
/** Proxy disabled. */
declare const CF_PROXY_OFF: RecordModifier;
/** Proxy enabled. */
declare const CF_PROXY_ON: RecordModifier;
/** Proxy+Railgun enabled. */
declare const CF_PROXY_FULL: RecordModifier;
/** Proxy default off for entire domain (the default) */
declare const CF_PROXY_DEFAULT_OFF: DomainModifier;
/** Proxy default on for entire domain */
declare const CF_PROXY_DEFAULT_ON: DomainModifier;
/** UniversalSSL off for entire domain */
declare const CF_UNIVERSALSSL_OFF: DomainModifier;
/** UniversalSSL on for entire domain */
declare const CF_UNIVERSALSSL_ON: DomainModifier;
/**
* Set default values for CLI variables. See: https://dnscontrol.org/cli-variables
*/
declare function CLI_DEFAULTS(vars: Record<string, unknown>): void;
/**
* `END` permits the last item to include a comma.
*
* ```js
* D("foo.com", ...
* A(...),
* A(...),
* A(...),
* END)
* ```
*/
declare const END: DomainModifier & RecordModifier;
/**
* Permit labels like `"foo.bar.com.bar.com"` (normally an error)
*
* ```js
* D("bar.com", ...
* A("foo.bar.com", "10.1.1.1", DISABLE_REPEATED_DOMAIN_CHECK),
* )
* ```
*/
declare const DISABLE_REPEATED_DOMAIN_CHECK: RecordModifier;