1
0
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:
Yuhui Xu
2022-11-15 11:40:08 -06:00
committed by GitHub
parent 1705906be5
commit 35818299c0
12 changed files with 470 additions and 2 deletions

1
OWNERS
View File

@ -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

View File

@ -34,6 +34,7 @@ Currently supported DNS providers:
- Domainnameshop (Domeneshop)
- Exoscale
- Gandi
- Gcore
- Google DNS
- Hetzner
- HEXONET

View File

@ -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&#39;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 &#39;dual hosting&#39; 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
View 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>

View File

@ -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
View File

@ -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
View File

@ -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=

View File

@ -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",

View File

@ -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"

View 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
View 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
}

View 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
}