mirror of
https://github.com/StackExchange/dnscontrol.git
synced 2024-05-11 05:55:12 +00:00
NEW PROVIDER: Gcore DNS (#1816)
This commit is contained in:
1
OWNERS
1
OWNERS
@ -15,6 +15,7 @@ providers/domainnameshop @SimenBai
|
||||
providers/easyname @tresni
|
||||
providers/exoscale @pierre-emmanuelJ
|
||||
providers/gandi_v5 @TomOnTime
|
||||
providers/gcore @xddxdd
|
||||
providers/gcloud @riyadhalnur
|
||||
providers/hedns @rblenkinsopp
|
||||
providers/hetzner @das7pad
|
||||
|
@ -34,6 +34,7 @@ Currently supported DNS providers:
|
||||
- Domainnameshop (Domeneshop)
|
||||
- Exoscale
|
||||
- Gandi
|
||||
- Gcore
|
||||
- Google DNS
|
||||
- Hetzner
|
||||
- HEXONET
|
||||
|
@ -23,6 +23,7 @@
|
||||
<th class="rotate"><div><span>EXOSCALE</span></div></th>
|
||||
<th class="rotate"><div><span>GANDI_V5</span></div></th>
|
||||
<th class="rotate"><div><span>GCLOUD</span></div></th>
|
||||
<th class="rotate"><div><span>GCORE</span></div></th>
|
||||
<th class="rotate"><div><span>HEDNS</span></div></th>
|
||||
<th class="rotate"><div><span>HETZNER</span></div></th>
|
||||
<th class="rotate"><div><span>HEXONET</span></div></th>
|
||||
@ -111,6 +112,9 @@
|
||||
<td class="danger">
|
||||
<i class="fa fa-times text-danger" aria-hidden="true"></i>
|
||||
</td>
|
||||
<td class="danger">
|
||||
<i class="fa fa-times text-danger" aria-hidden="true"></i>
|
||||
</td>
|
||||
<td class="danger" data-toggle="tooltip" data-container="body" data-placement="top" title="Actively maintained provider module.">
|
||||
<i class="fa has-tooltip fa-times text-danger" aria-hidden="true"></i>
|
||||
</td>
|
||||
@ -243,6 +247,9 @@
|
||||
<td class="success">
|
||||
<i class="fa fa-check text-success" aria-hidden="true"></i>
|
||||
</td>
|
||||
<td class="success">
|
||||
<i class="fa fa-check text-success" aria-hidden="true"></i>
|
||||
</td>
|
||||
<td class="danger">
|
||||
<i class="fa fa-times text-danger" aria-hidden="true"></i>
|
||||
</td>
|
||||
@ -363,6 +370,9 @@
|
||||
<td class="danger">
|
||||
<i class="fa fa-times text-danger" aria-hidden="true"></i>
|
||||
</td>
|
||||
<td class="danger">
|
||||
<i class="fa fa-times text-danger" aria-hidden="true"></i>
|
||||
</td>
|
||||
<td class="success">
|
||||
<i class="fa fa-check text-success" aria-hidden="true"></i>
|
||||
</td>
|
||||
@ -469,6 +479,9 @@
|
||||
<i class="fa has-tooltip fa-check text-success" aria-hidden="true"></i>
|
||||
</td>
|
||||
<td><i class="fa fa-minus dim"></i></td>
|
||||
<td class="danger">
|
||||
<i class="fa fa-times text-danger" aria-hidden="true"></i>
|
||||
</td>
|
||||
<td class="success">
|
||||
<i class="fa fa-check text-success" aria-hidden="true"></i>
|
||||
</td>
|
||||
@ -560,6 +573,9 @@
|
||||
<td class="danger">
|
||||
<i class="fa fa-times text-danger" aria-hidden="true"></i>
|
||||
</td>
|
||||
<td class="danger">
|
||||
<i class="fa fa-times text-danger" aria-hidden="true"></i>
|
||||
</td>
|
||||
<td><i class="fa fa-minus dim"></i></td>
|
||||
<td><i class="fa fa-minus dim"></i></td>
|
||||
<td class="info" data-toggle="tooltip" data-container="body" data-placement="top" title="Supported but not implemented yet.">
|
||||
@ -624,8 +640,8 @@
|
||||
<td class="success">
|
||||
<i class="fa fa-check text-success" aria-hidden="true"></i>
|
||||
</td>
|
||||
<td class="success" data-toggle="tooltip" data-container="body" data-placement="top" title="Semicolons not supported in issue/issuewild fields.">
|
||||
<a href="https://www.digitalocean.com/docs/networking/dns/how-to/create-caa-records"><i class="fa has-tooltip fa-check text-success" aria-hidden="true"></i></a>
|
||||
<td class="success">
|
||||
<i class="fa fa-check text-success" aria-hidden="true"></i>
|
||||
</td>
|
||||
<td class="success">
|
||||
<i class="fa fa-check text-success" aria-hidden="true"></i>
|
||||
@ -659,6 +675,9 @@
|
||||
<td class="success">
|
||||
<i class="fa fa-check text-success" aria-hidden="true"></i>
|
||||
</td>
|
||||
<td class="success">
|
||||
<i class="fa fa-check text-success" aria-hidden="true"></i>
|
||||
</td>
|
||||
<td><i class="fa fa-minus dim"></i></td>
|
||||
<td class="success">
|
||||
<i class="fa fa-check text-success" aria-hidden="true"></i>
|
||||
@ -753,6 +772,9 @@
|
||||
<td class="success">
|
||||
<i class="fa fa-check text-success" aria-hidden="true"></i>
|
||||
</td>
|
||||
<td class="danger">
|
||||
<i class="fa fa-times text-danger" aria-hidden="true"></i>
|
||||
</td>
|
||||
<td class="success">
|
||||
<i class="fa fa-check text-success" aria-hidden="true"></i>
|
||||
</td>
|
||||
@ -847,6 +869,9 @@
|
||||
<td><i class="fa fa-minus dim"></i></td>
|
||||
<td><i class="fa fa-minus dim"></i></td>
|
||||
<td><i class="fa fa-minus dim"></i></td>
|
||||
<td class="danger">
|
||||
<i class="fa fa-times text-danger" aria-hidden="true"></i>
|
||||
</td>
|
||||
<td class="success">
|
||||
<i class="fa fa-check text-success" aria-hidden="true"></i>
|
||||
</td>
|
||||
@ -938,6 +963,7 @@
|
||||
<td><i class="fa fa-minus dim"></i></td>
|
||||
<td><i class="fa fa-minus dim"></i></td>
|
||||
<td><i class="fa fa-minus dim"></i></td>
|
||||
<td><i class="fa fa-minus dim"></i></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th class="row-header" style="text-decoration: underline;" data-toggle="tooltip" data-container="body" data-placement="top" title="Driver has explicitly implemented SRV record management">SRV</th>
|
||||
@ -991,6 +1017,9 @@
|
||||
<td class="success">
|
||||
<i class="fa fa-check text-success" aria-hidden="true"></i>
|
||||
</td>
|
||||
<td class="success" data-toggle="tooltip" data-container="body" data-placement="top" title="G-Core doesn't support SRV records with empty targets">
|
||||
<i class="fa has-tooltip fa-check text-success" aria-hidden="true"></i>
|
||||
</td>
|
||||
<td class="success">
|
||||
<i class="fa fa-check text-success" aria-hidden="true"></i>
|
||||
</td>
|
||||
@ -1101,6 +1130,9 @@
|
||||
<td class="success">
|
||||
<i class="fa fa-check text-success" aria-hidden="true"></i>
|
||||
</td>
|
||||
<td class="danger">
|
||||
<i class="fa fa-times text-danger" aria-hidden="true"></i>
|
||||
</td>
|
||||
<td class="success">
|
||||
<i class="fa fa-check text-success" aria-hidden="true"></i>
|
||||
</td>
|
||||
@ -1199,6 +1231,9 @@
|
||||
<td class="danger">
|
||||
<i class="fa fa-times text-danger" aria-hidden="true"></i>
|
||||
</td>
|
||||
<td class="danger">
|
||||
<i class="fa fa-times text-danger" aria-hidden="true"></i>
|
||||
</td>
|
||||
<td class="success">
|
||||
<i class="fa fa-check text-success" aria-hidden="true"></i>
|
||||
</td>
|
||||
@ -1286,6 +1321,7 @@
|
||||
<td><i class="fa fa-minus dim"></i></td>
|
||||
<td><i class="fa fa-minus dim"></i></td>
|
||||
<td><i class="fa fa-minus dim"></i></td>
|
||||
<td><i class="fa fa-minus dim"></i></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th class="row-header" style="text-decoration: underline;" data-toggle="tooltip" data-container="body" data-placement="top" title="Provider supports Route 53 limited ALIAS">R53_ALIAS</th>
|
||||
@ -1309,6 +1345,7 @@
|
||||
<td><i class="fa fa-minus dim"></i></td>
|
||||
<td><i class="fa fa-minus dim"></i></td>
|
||||
<td><i class="fa fa-minus dim"></i></td>
|
||||
<td><i class="fa fa-minus dim"></i></td>
|
||||
<td class="danger" data-toggle="tooltip" data-container="body" data-placement="top" title="Using ALIAS is possible through our extended DNS (X-DNS) service. Feel free to get in touch with us.">
|
||||
<i class="fa has-tooltip fa-times text-danger" aria-hidden="true"></i>
|
||||
</td>
|
||||
@ -1362,6 +1399,7 @@
|
||||
<td><i class="fa fa-minus dim"></i></td>
|
||||
<td><i class="fa fa-minus dim"></i></td>
|
||||
<td><i class="fa fa-minus dim"></i></td>
|
||||
<td><i class="fa fa-minus dim"></i></td>
|
||||
<td class="danger">
|
||||
<i class="fa fa-times text-danger" aria-hidden="true"></i>
|
||||
</td>
|
||||
@ -1431,6 +1469,9 @@
|
||||
<td class="danger">
|
||||
<i class="fa fa-times text-danger" aria-hidden="true"></i>
|
||||
</td>
|
||||
<td class="danger">
|
||||
<i class="fa fa-times text-danger" aria-hidden="true"></i>
|
||||
</td>
|
||||
<td><i class="fa fa-minus dim"></i></td>
|
||||
<td class="success">
|
||||
<i class="fa fa-check text-success" aria-hidden="true"></i>
|
||||
@ -1514,6 +1555,7 @@
|
||||
<td><i class="fa fa-minus dim"></i></td>
|
||||
<td><i class="fa fa-minus dim"></i></td>
|
||||
<td><i class="fa fa-minus dim"></i></td>
|
||||
<td><i class="fa fa-minus dim"></i></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th class="row-header" style="text-decoration: underline;" data-toggle="tooltip" data-container="body" data-placement="top" title="This provider is recommended for use in 'dual hosting' scenarios. Usually this means the provider allows full control over the apex NS records">dual host</th>
|
||||
@ -1573,6 +1615,9 @@
|
||||
<td class="success">
|
||||
<i class="fa fa-check text-success" aria-hidden="true"></i>
|
||||
</td>
|
||||
<td class="success">
|
||||
<i class="fa fa-check text-success" aria-hidden="true"></i>
|
||||
</td>
|
||||
<td><i class="fa fa-minus dim"></i></td>
|
||||
<td class="success">
|
||||
<i class="fa fa-check text-success" aria-hidden="true"></i>
|
||||
@ -1689,6 +1734,9 @@
|
||||
<td class="success">
|
||||
<i class="fa fa-check text-success" aria-hidden="true"></i>
|
||||
</td>
|
||||
<td class="success">
|
||||
<i class="fa fa-check text-success" aria-hidden="true"></i>
|
||||
</td>
|
||||
<td class="danger">
|
||||
<i class="fa fa-times text-danger" aria-hidden="true"></i>
|
||||
</td>
|
||||
@ -1827,6 +1875,9 @@
|
||||
<td class="success">
|
||||
<i class="fa fa-check text-success" aria-hidden="true"></i>
|
||||
</td>
|
||||
<td class="success">
|
||||
<i class="fa fa-check text-success" aria-hidden="true"></i>
|
||||
</td>
|
||||
<td class="danger">
|
||||
<i class="fa fa-times text-danger" aria-hidden="true"></i>
|
||||
</td>
|
||||
@ -1931,6 +1982,9 @@
|
||||
<td class="success">
|
||||
<i class="fa fa-check text-success" aria-hidden="true"></i>
|
||||
</td>
|
||||
<td class="success">
|
||||
<i class="fa fa-check text-success" aria-hidden="true"></i>
|
||||
</td>
|
||||
<td class="info">
|
||||
<i class="fa fa-circle-o text-info" aria-hidden="true"></i>
|
||||
</td>
|
||||
|
43
docs/_providers/gcore.md
Normal file
43
docs/_providers/gcore.md
Normal file
@ -0,0 +1,43 @@
|
||||
---
|
||||
name: Gcore
|
||||
title: Gcore Provider
|
||||
layout: default
|
||||
jsId: GCORE
|
||||
---
|
||||
# Gcore Provider
|
||||
## Configuration
|
||||
|
||||
To use this provider, add an entry to `creds.json` with `TYPE` set to `GCORE`
|
||||
along with a Gcore account API token.
|
||||
|
||||
Example:
|
||||
|
||||
```json
|
||||
{
|
||||
"gcore": {
|
||||
"TYPE": "GCORE",
|
||||
"api-key": "your-gcore-api-key"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Metadata
|
||||
This provider does not recognize any special metadata fields unique to Gcore.
|
||||
|
||||
## Usage
|
||||
An example `dnsconfig.js` configuration:
|
||||
|
||||
```js
|
||||
var REG_NONE = NewRegistrar("none"); // No registrar.
|
||||
var DSP_GCORE = NewDnsProvider("gcore"); // Gcore
|
||||
|
||||
D("example.tld", REG_NONE, DnsProvider(DSP_GCORE),
|
||||
A("test", "1.2.3.4")
|
||||
);
|
||||
```
|
||||
|
||||
## Activation
|
||||
|
||||
DNSControl depends on a Gcore account API token.
|
||||
|
||||
You can obtain your API token on this page: <https://accounts.gcore.com/profile/api-tokens>
|
@ -85,6 +85,7 @@ Providers in this category and their maintainers are:
|
||||
* `EASYNAME` @tresni
|
||||
* `EXOSCALE` @pierre-emmanuelJ
|
||||
* `GANDI_V5` @TomOnTime
|
||||
* `GCORE` @xddxdd
|
||||
* `HEDNS` @rblenkinsopp
|
||||
* `HETZNER` @das7pad
|
||||
* `HEXONET` @papakai
|
||||
|
2
go.mod
2
go.mod
@ -60,6 +60,7 @@ require (
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/G-Core/gcore-dns-sdk-go v0.2.3
|
||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510
|
||||
github.com/mattn/go-isatty v0.0.16
|
||||
github.com/vultr/govultr/v2 v2.17.2
|
||||
@ -152,6 +153,7 @@ require (
|
||||
go.uber.org/atomic v1.9.0 // indirect
|
||||
golang.org/x/crypto v0.1.0 // indirect
|
||||
golang.org/x/mod v0.6.0 // indirect
|
||||
golang.org/x/sync v0.1.0 // indirect
|
||||
golang.org/x/sys v0.1.0 // indirect
|
||||
golang.org/x/text v0.4.0 // indirect
|
||||
golang.org/x/time v0.0.0-20220922220347-f3bd1da661af // indirect
|
||||
|
3
go.sum
3
go.sum
@ -19,6 +19,8 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03
|
||||
github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=
|
||||
github.com/DisposaBoy/JsonConfigReader v0.0.0-20201129172854-99cf318d67e7 h1:AJKJCKcb/psppPl/9CUiQQnTG+Bce0/cIweD5w5Q7aQ=
|
||||
github.com/DisposaBoy/JsonConfigReader v0.0.0-20201129172854-99cf318d67e7/go.mod h1:GCzqZQHydohgVLSIqRKZeTt8IGb1Y4NaFfim3H40uUI=
|
||||
github.com/G-Core/gcore-dns-sdk-go v0.2.3 h1:WODi+qWlZyF7E7SH8rq/DCACa/Zhsuhu1h0DuFJc2Yg=
|
||||
github.com/G-Core/gcore-dns-sdk-go v0.2.3/go.mod h1:TM+VaDvBPObF+x085lS3i0kc2OPAkuW2c4Leg7Pe6jI=
|
||||
github.com/PuerkitoBio/goquery v1.8.0 h1:PJTF7AmFCFKk1N6V6jmKfrNH9tV5pNE6lZMkG0gta/U=
|
||||
github.com/PuerkitoBio/goquery v1.8.0/go.mod h1:ypIiRMtY7COPGk+I/YbZLbxsxn9g5ejnI2HSMtkjZvI=
|
||||
github.com/TomOnTime/utfutil v0.0.0-20210710122150-437f72b26edf h1:+GdVyvpzTy3UFAS1+hbTqm9Mk0U1Xrocm28s/E2GWz0=
|
||||
@ -573,6 +575,7 @@ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJ
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
|
@ -94,6 +94,10 @@
|
||||
"project_id": "$GCLOUD_PROJECT",
|
||||
"type": "$GCLOUD_TYPE"
|
||||
},
|
||||
"GCORE": {
|
||||
"api-key": "$GCORE_API_KEY",
|
||||
"domain": "$GCORE_DOMAIN"
|
||||
},
|
||||
"HEDNS": {
|
||||
"domain": "$HEDNS_DOMAIN",
|
||||
"password": "$HEDNS_PASSWORD",
|
||||
|
@ -21,6 +21,7 @@ import (
|
||||
_ "github.com/StackExchange/dnscontrol/v3/providers/exoscale"
|
||||
_ "github.com/StackExchange/dnscontrol/v3/providers/gandiv5"
|
||||
_ "github.com/StackExchange/dnscontrol/v3/providers/gcloud"
|
||||
_ "github.com/StackExchange/dnscontrol/v3/providers/gcore"
|
||||
_ "github.com/StackExchange/dnscontrol/v3/providers/hedns"
|
||||
_ "github.com/StackExchange/dnscontrol/v3/providers/hetzner"
|
||||
_ "github.com/StackExchange/dnscontrol/v3/providers/hexonet"
|
||||
|
15
providers/gcore/auditrecords.go
Normal file
15
providers/gcore/auditrecords.go
Normal file
@ -0,0 +1,15 @@
|
||||
package gcore
|
||||
|
||||
import (
|
||||
"github.com/StackExchange/dnscontrol/v3/models"
|
||||
"github.com/StackExchange/dnscontrol/v3/pkg/rejectif"
|
||||
)
|
||||
|
||||
// AuditRecords returns a list of errors corresponding to the records
|
||||
// that aren't supported by this provider. If all records are
|
||||
// supported, an empty list is returned.
|
||||
func AuditRecords(records []*models.RecordConfig) []error {
|
||||
a := rejectif.Auditor{}
|
||||
a.Add("SRV", rejectif.SrvHasNullTarget)
|
||||
return a.Audit(records)
|
||||
}
|
107
providers/gcore/convert.go
Normal file
107
providers/gcore/convert.go
Normal file
@ -0,0 +1,107 @@
|
||||
package gcore
|
||||
|
||||
// Convert the provider's native record description to models.RecordConfig.
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
dnssdk "github.com/G-Core/gcore-dns-sdk-go"
|
||||
"github.com/StackExchange/dnscontrol/v3/models"
|
||||
"github.com/StackExchange/dnscontrol/v3/pkg/printer"
|
||||
)
|
||||
|
||||
// nativeToRecord takes a DNS record from G-Core and returns a native RecordConfig struct.
|
||||
func nativeToRecords(n dnssdk.RRSet, zoneName string, recName string, recType string) ([]*models.RecordConfig, error) {
|
||||
var rcs []*models.RecordConfig
|
||||
|
||||
// Split G-Core's RRset into individual records
|
||||
for _, value := range n.Records {
|
||||
rc := &models.RecordConfig{
|
||||
TTL: uint32(n.TTL),
|
||||
Original: n,
|
||||
}
|
||||
rc.SetLabelFromFQDN(recName, zoneName)
|
||||
switch recType {
|
||||
case "CAA": // G-Core API don't need quotes around CAA with whitespace
|
||||
if len(value.Content) != 3 {
|
||||
return nil, errors.New("incorrect number of fields in G-Core's CAA record")
|
||||
}
|
||||
|
||||
parts := make([]string, len(value.Content))
|
||||
for i := range value.Content {
|
||||
parts[i] = fmt.Sprint(value.Content[i])
|
||||
}
|
||||
|
||||
flag, tag, target := parts[0], parts[1], parts[2]
|
||||
if err := rc.SetTargetCAAStrings(flag, tag, target); err != nil {
|
||||
return nil, fmt.Errorf("unparsable record received from G-Core: %w", err)
|
||||
}
|
||||
|
||||
default: // "A", "AAAA", "CAA", "NS", "CNAME", "MX", "PTR", "SRV", "TXT"
|
||||
if err := rc.PopulateFromString(recType, value.ContentToString(), zoneName); err != nil {
|
||||
return nil, fmt.Errorf("unparsable record received from G-Core: %w", err)
|
||||
}
|
||||
}
|
||||
rcs = append(rcs, rc)
|
||||
}
|
||||
|
||||
return rcs, nil
|
||||
}
|
||||
|
||||
func recordsToNative(rcs []*models.RecordConfig, expectedKey models.RecordKey) *dnssdk.RRSet {
|
||||
// Merge DNSControl records into G-Core RRsets
|
||||
|
||||
var result *dnssdk.RRSet
|
||||
|
||||
for _, r := range rcs {
|
||||
label := r.GetLabel()
|
||||
if label == "@" {
|
||||
label = ""
|
||||
}
|
||||
key := r.Key()
|
||||
|
||||
if key != expectedKey {
|
||||
continue
|
||||
}
|
||||
|
||||
var rr dnssdk.ResourceRecord
|
||||
switch key.Type {
|
||||
case "CAA": // G-Core API don't need quotes around CAA with whitespace
|
||||
rr = dnssdk.ResourceRecord{
|
||||
Content: []interface{}{
|
||||
int64(r.CaaFlag),
|
||||
r.CaaTag,
|
||||
r.GetTargetField(),
|
||||
},
|
||||
Meta: nil,
|
||||
Enabled: true,
|
||||
}
|
||||
default:
|
||||
rr = dnssdk.ResourceRecord{
|
||||
Content: dnssdk.ContentFromValue(key.Type, r.GetTargetCombined()),
|
||||
Meta: nil,
|
||||
Enabled: true,
|
||||
}
|
||||
}
|
||||
|
||||
if result == nil {
|
||||
result = &dnssdk.RRSet{
|
||||
TTL: int(r.TTL),
|
||||
Filters: nil,
|
||||
Records: []dnssdk.ResourceRecord{rr},
|
||||
}
|
||||
} else {
|
||||
result.Records = append(result.Records, rr)
|
||||
|
||||
if int(r.TTL) != result.TTL {
|
||||
printer.Warnf("All TTLs for a rrset (%v) must be the same. Using smaller of %v and %v.\n", key, r.TTL, result.TTL)
|
||||
if int(r.TTL) < result.TTL {
|
||||
result.TTL = int(r.TTL)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
236
providers/gcore/gcoreProvider.go
Normal file
236
providers/gcore/gcoreProvider.go
Normal file
@ -0,0 +1,236 @@
|
||||
package gcore
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/StackExchange/dnscontrol/v3/models"
|
||||
"github.com/StackExchange/dnscontrol/v3/pkg/diff"
|
||||
"github.com/StackExchange/dnscontrol/v3/providers"
|
||||
|
||||
dnssdk "github.com/G-Core/gcore-dns-sdk-go"
|
||||
)
|
||||
|
||||
/*
|
||||
G-Core API DNS provider:
|
||||
Info required in `creds.json`:
|
||||
- api-key
|
||||
*/
|
||||
|
||||
type gcoreProvider struct {
|
||||
provider *dnssdk.Client
|
||||
ctx context.Context
|
||||
}
|
||||
|
||||
// NewGCore creates the provider.
|
||||
func NewGCore(m map[string]string, metadata json.RawMessage) (providers.DNSServiceProvider, error) {
|
||||
if m["api-key"] == "" {
|
||||
return nil, fmt.Errorf("missing G-Core API key")
|
||||
}
|
||||
|
||||
c := &gcoreProvider{
|
||||
provider: dnssdk.NewClient(dnssdk.PermanentAPIKeyAuth(m["api-key"])),
|
||||
ctx: context.TODO(),
|
||||
}
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
var features = providers.DocumentationNotes{
|
||||
providers.CanAutoDNSSEC: providers.Cannot(),
|
||||
providers.CanGetZones: providers.Can(),
|
||||
providers.CanUseAlias: providers.Cannot(),
|
||||
providers.CanUseCAA: providers.Can(),
|
||||
providers.CanUseDS: providers.Cannot(),
|
||||
providers.CanUseNAPTR: providers.Cannot(),
|
||||
providers.CanUsePTR: providers.Cannot(),
|
||||
providers.CanUseSRV: providers.Can("G-Core doesn't support SRV records with empty targets"),
|
||||
providers.CanUseSSHFP: providers.Cannot(),
|
||||
providers.CanUseTLSA: providers.Cannot(),
|
||||
providers.DocCreateDomains: providers.Can(),
|
||||
providers.DocDualHost: providers.Can(),
|
||||
providers.DocOfficiallySupported: providers.Cannot(),
|
||||
}
|
||||
|
||||
var defaultNameServerNames = []string{
|
||||
"ns1.gcorelabs.net",
|
||||
"ns2.gcdn.services",
|
||||
}
|
||||
|
||||
func init() {
|
||||
fns := providers.DspFuncs{
|
||||
Initializer: NewGCore,
|
||||
RecordAuditor: AuditRecords,
|
||||
}
|
||||
providers.RegisterDomainServiceProviderType("GCORE", fns, features)
|
||||
}
|
||||
|
||||
// GetNameservers returns the nameservers for a domain.
|
||||
func (c *gcoreProvider) GetNameservers(domain string) ([]*models.Nameserver, error) {
|
||||
return models.ToNameservers(defaultNameServerNames)
|
||||
}
|
||||
|
||||
func (c *gcoreProvider) GetDomainCorrections(dc *models.DomainConfig) ([]*models.Correction, error) {
|
||||
existing, err := c.GetZoneRecords(dc.Name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
models.PostProcessRecords(existing)
|
||||
clean := PrepFoundRecords(existing)
|
||||
PrepDesiredRecords(dc)
|
||||
return c.GenerateDomainCorrections(dc, clean)
|
||||
}
|
||||
|
||||
// GetZoneRecords gets the records of a zone and returns them in RecordConfig format.
|
||||
func (c *gcoreProvider) GetZoneRecords(domain string) (models.Records, error) {
|
||||
zone, err := c.provider.Zone(c.ctx, domain)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Convert RRsets to DNSControl format on the fly
|
||||
existingRecords := []*models.RecordConfig{}
|
||||
|
||||
// We cannot directly use Zone's ShortAnswers
|
||||
// they aren't complete for CAA & SRV
|
||||
for _, rec := range zone.Records {
|
||||
rrset, err := c.provider.RRSet(c.ctx, zone.Name, rec.Name, rec.Type)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
nativeRecords, err := nativeToRecords(rrset, zone.Name, rec.Name, rec.Type)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
existingRecords = append(existingRecords, nativeRecords...)
|
||||
}
|
||||
|
||||
return existingRecords, nil
|
||||
}
|
||||
|
||||
// EnsureDomainExists returns an error if domain doesn't exist.
|
||||
func (c *gcoreProvider) EnsureDomainExists(domain string) error {
|
||||
zones, err := c.provider.Zones(c.ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, zone := range zones {
|
||||
if zone.Name == domain {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
_, err = c.provider.CreateZone(c.ctx, domain)
|
||||
return err
|
||||
}
|
||||
|
||||
// PrepFoundRecords munges any records to make them compatible with
|
||||
// this provider. Usually this is a no-op.
|
||||
func PrepFoundRecords(recs models.Records) models.Records {
|
||||
// If there are records that need to be modified, removed, etc. we
|
||||
// do it here. Usually this is a no-op.
|
||||
return recs
|
||||
}
|
||||
|
||||
// PrepDesiredRecords munges any records to best suit this provider.
|
||||
func PrepDesiredRecords(dc *models.DomainConfig) {
|
||||
dc.Punycode()
|
||||
}
|
||||
|
||||
func generateChangeMsg(updates []string) string {
|
||||
return strings.Join(updates, "\n")
|
||||
}
|
||||
|
||||
// GenerateDomainCorrections takes the desired and existing records
|
||||
// and produces a Correction list. The correction list is simply
|
||||
// a list of functions to call to actually make the desired
|
||||
// correction, and a message to output to the user when the change is
|
||||
// made.
|
||||
func (c *gcoreProvider) GenerateDomainCorrections(dc *models.DomainConfig, existing models.Records) ([]*models.Correction, error) {
|
||||
|
||||
var corrections = []*models.Correction{}
|
||||
|
||||
// diff existing vs. current.
|
||||
differ := diff.New(dc)
|
||||
keysToUpdate, err := differ.ChangedGroups(existing)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(keysToUpdate) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
desiredRecords := dc.Records.GroupedByKey()
|
||||
existingRecords := existing.GroupedByKey()
|
||||
|
||||
// First pass: delete records to avoid coexisting of conflicting types
|
||||
for label := range keysToUpdate {
|
||||
if _, ok := desiredRecords[label]; !ok {
|
||||
// record deleted in update
|
||||
// Copy all params to avoid overwrites
|
||||
zone := dc.Name
|
||||
name := label.NameFQDN
|
||||
typ := label.Type
|
||||
msg := generateChangeMsg(keysToUpdate[label])
|
||||
corrections = append(corrections, &models.Correction{
|
||||
Msg: msg,
|
||||
F: func() error {
|
||||
return c.provider.DeleteRRSet(c.ctx, zone, name, typ)
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Second pass: create and update records
|
||||
for label := range keysToUpdate {
|
||||
if _, ok := desiredRecords[label]; !ok {
|
||||
// record deleted in update
|
||||
// do nothing here
|
||||
|
||||
} else if _, ok := existingRecords[label]; !ok {
|
||||
// record created in update
|
||||
record := recordsToNative(desiredRecords[label], label)
|
||||
if record == nil {
|
||||
panic("No records matching label")
|
||||
}
|
||||
|
||||
// Copy all params to avoid overwrites
|
||||
zone := dc.Name
|
||||
name := label.NameFQDN
|
||||
typ := label.Type
|
||||
rec := *record
|
||||
msg := generateChangeMsg(keysToUpdate[label])
|
||||
corrections = append(corrections, &models.Correction{
|
||||
Msg: msg,
|
||||
F: func() error {
|
||||
return c.provider.CreateRRSet(c.ctx, zone, name, typ, rec)
|
||||
},
|
||||
})
|
||||
|
||||
} else {
|
||||
// record modified in update
|
||||
record := recordsToNative(desiredRecords[label], label)
|
||||
if record == nil {
|
||||
panic("No records matching label")
|
||||
}
|
||||
|
||||
// Copy all params to avoid overwrites
|
||||
zone := dc.Name
|
||||
name := label.NameFQDN
|
||||
typ := label.Type
|
||||
rec := *record
|
||||
msg := generateChangeMsg(keysToUpdate[label])
|
||||
corrections = append(corrections, &models.Correction{
|
||||
Msg: msg,
|
||||
F: func() error {
|
||||
return c.provider.UpdateRRSet(c.ctx, zone, name, typ, rec)
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return corrections, nil
|
||||
}
|
Reference in New Issue
Block a user