Update dependencies and go version

This commit is contained in:
Balakrishnan Balasubramanian 2024-03-31 12:45:54 -04:00
parent c2fc6d587d
commit bc1c3842ee
10 changed files with 186 additions and 44 deletions

View File

@ -39,7 +39,7 @@ Usage of cloudflare-dns-cli:
IP address (default 127.0.0.1) IP address (default 127.0.0.1)
-m Set mx record with cname value -m Set mx record with cname value
-o string -o string
Path to save all records as json, e.g. ./records.json Path to save all records as json, e.g. ./records.json, '-' for stdout
-s string -s string
Subdomain, e.g. blog. Use @ for root (default "<UNSET>") Subdomain, e.g. blog. Use @ for root (default "<UNSET>")
-x Delete records of subdomain -x Delete records of subdomain

6
go.mod
View File

@ -1,8 +1,8 @@
module go.balki.me/cloudflare-dns-cli module go.balki.me/cloudflare-dns-cli
go 1.21 go 1.22.1
require ( require (
github.com/libdns/cloudflare v0.1.0 github.com/libdns/cloudflare v0.1.1
github.com/libdns/libdns v0.2.1 github.com/libdns/libdns v0.2.2
) )

9
go.sum
View File

@ -1,5 +1,4 @@
github.com/libdns/cloudflare v0.1.0 h1:93WkJaGaiXCe353LHEP36kAWCUw0YjFqwhkBkU2/iic= github.com/libdns/cloudflare v0.1.1 h1:FVPfWwP8zZCqj268LZjmkDleXlHPlFU9KC4OJ3yn054=
github.com/libdns/cloudflare v0.1.0/go.mod h1:a44IP6J1YH6nvcNl1PverfJviADgXUnsozR3a7vBKN8= github.com/libdns/cloudflare v0.1.1/go.mod h1:9VK91idpOjg6v7/WbjkEW49bSCxj00ALesIFDhJ8PBU=
github.com/libdns/libdns v0.2.0/go.mod h1:yQCXzk1lEZmmCPa857bnk4TsOiqYasqpyOEeSObbb40= github.com/libdns/libdns v0.2.2 h1:O6ws7bAfRPaBsgAYt8MDe2HcNBGC29hkZ9MX2eUSX3s=
github.com/libdns/libdns v0.2.1 h1:Wu59T7wSHRgtA0cfxC+n1c/e+O3upJGWytknkmFEDis= github.com/libdns/libdns v0.2.2/go.mod h1:4Bj9+5CQiNMVGf87wjX4CY3HQJypUHRuLvlsfsZqLWQ=
github.com/libdns/libdns v0.2.1/go.mod h1:yQCXzk1lEZmmCPa857bnk4TsOiqYasqpyOEeSObbb40=

View File

@ -63,9 +63,9 @@ func genRecord() (libdns.Record, error) {
func selectRecordsToDelete(recs []libdns.Record) []libdns.Record { func selectRecordsToDelete(recs []libdns.Record) []libdns.Record {
name := func() string { name := func() string {
if sub == "@" { if sub == "@" {
return domain return ""
} }
return fmt.Sprintf("%s.%s", sub, domain) return sub
}() }()
recs = func() (result []libdns.Record) { recs = func() (result []libdns.Record) {
for _, r := range recs { for _, r := range recs {

View File

@ -12,7 +12,11 @@ import (
) )
func (p *Provider) createRecord(ctx context.Context, zoneInfo cfZone, record libdns.Record) (cfDNSRecord, error) { func (p *Provider) createRecord(ctx context.Context, zoneInfo cfZone, record libdns.Record) (cfDNSRecord, error) {
jsonBytes, err := json.Marshal(cloudflareRecord(record)) cfRec, err := cloudflareRecord(record)
if err != nil {
return cfDNSRecord{}, err
}
jsonBytes, err := json.Marshal(cfRec)
if err != nil { if err != nil {
return cfDNSRecord{}, err return cfDNSRecord{}, err
} }

View File

@ -2,6 +2,7 @@ package cloudflare
import ( import (
"encoding/json" "encoding/json"
"strings"
"time" "time"
"github.com/libdns/libdns" "github.com/libdns/libdns"
@ -61,7 +62,7 @@ type cfDNSRecord struct {
ZoneName string `json:"zone_name,omitempty"` ZoneName string `json:"zone_name,omitempty"`
CreatedOn time.Time `json:"created_on,omitempty"` CreatedOn time.Time `json:"created_on,omitempty"`
ModifiedOn time.Time `json:"modified_on,omitempty"` ModifiedOn time.Time `json:"modified_on,omitempty"`
Data *struct { Data struct {
// LOC // LOC
LatDegrees int `json:"lat_degrees,omitempty"` LatDegrees int `json:"lat_degrees,omitempty"`
LatMinutes int `json:"lat_minutes,omitempty"` LatMinutes int `json:"lat_minutes,omitempty"`
@ -80,9 +81,9 @@ type cfDNSRecord struct {
Service string `json:"service,omitempty"` Service string `json:"service,omitempty"`
Proto string `json:"proto,omitempty"` Proto string `json:"proto,omitempty"`
Name string `json:"name,omitempty"` Name string `json:"name,omitempty"`
Priority int `json:"priority,omitempty"` Priority uint `json:"priority,omitempty"`
Weight int `json:"weight,omitempty"` Weight uint `json:"weight,omitempty"`
Port int `json:"port,omitempty"` Port uint `json:"port,omitempty"`
Target string `json:"target,omitempty"` Target string `json:"target,omitempty"`
// DNSKEY // DNSKEY
@ -109,6 +110,18 @@ type cfDNSRecord struct {
} }
func (r cfDNSRecord) libdnsRecord(zone string) libdns.Record { func (r cfDNSRecord) libdnsRecord(zone string) libdns.Record {
if r.Type == "SRV" {
srv := libdns.SRV{
Service: strings.TrimPrefix(r.Data.Service, "_"),
Proto: strings.TrimPrefix(r.Data.Proto, "_"),
Name: r.Data.Name,
Priority: r.Data.Priority,
Weight: r.Data.Weight,
Port: r.Data.Port,
Target: r.Data.Target,
}
return srv.ToRecord()
}
return libdns.Record{ return libdns.Record{
Type: r.Type, Type: r.Type,
Name: libdns.RelativeName(r.Name, zone), Name: libdns.RelativeName(r.Name, zone),
@ -118,14 +131,29 @@ func (r cfDNSRecord) libdnsRecord(zone string) libdns.Record {
} }
} }
func cloudflareRecord(r libdns.Record) cfDNSRecord { func cloudflareRecord(r libdns.Record) (cfDNSRecord, error) {
return cfDNSRecord{ rec := cfDNSRecord{
ID: r.ID, ID: r.ID,
Type: r.Type, Type: r.Type,
Name: r.Name,
Content: r.Value,
TTL: int(r.TTL.Seconds()), TTL: int(r.TTL.Seconds()),
} }
if r.Type == "SRV" {
srv, err := r.ToSRV()
if err != nil {
return cfDNSRecord{}, err
}
rec.Data.Service = "_" + srv.Service
rec.Data.Priority = srv.Priority
rec.Data.Weight = srv.Weight
rec.Data.Proto = "_" + srv.Proto
rec.Data.Name = srv.Name
rec.Data.Port = srv.Port
rec.Data.Target = srv.Target
} else {
rec.Name = r.Name
rec.Content = r.Value
}
return rec, nil
} }
// All API responses have this structure. // All API responses have this structure.
@ -135,8 +163,12 @@ type cfResponse struct {
Errors []struct { Errors []struct {
Code int `json:"code"` Code int `json:"code"`
Message string `json:"message"` Message string `json:"message"`
ErrorChain []struct {
Code int `json:"code"`
Message string `json:"message"`
} `json:"error_chain,omitempty"`
} `json:"errors,omitempty"` } `json:"errors,omitempty"`
Messages []interface{} `json:"messages,omitempty"` Messages []any `json:"messages,omitempty"`
ResultInfo *cfResultInfo `json:"result_info,omitempty"` ResultInfo *cfResultInfo `json:"result_info,omitempty"`
} }

View File

@ -131,7 +131,10 @@ func (p *Provider) SetRecords(ctx context.Context, zone string, records []libdns
var results []libdns.Record var results []libdns.Record
for _, rec := range records { for _, rec := range records {
oldRec := cloudflareRecord(rec) oldRec, err := cloudflareRecord(rec)
if err != nil {
return nil, err
}
oldRec.ZoneID = zoneInfo.ID oldRec.ZoneID = zoneInfo.ID
if rec.ID == "" { if rec.ID == "" {
// the record might already exist, even if we don't know the ID yet // the record might already exist, even if we don't know the ID yet
@ -155,7 +158,11 @@ func (p *Provider) SetRecords(ctx context.Context, zone string, records []libdns
oldRec.ID = matches[0].ID oldRec.ID = matches[0].ID
} }
// record exists; update it // record exists; update it
result, err := p.updateRecord(ctx, oldRec, cloudflareRecord(rec)) cfRec, err := cloudflareRecord(rec)
if err != nil {
return nil, err
}
result, err := p.updateRecord(ctx, oldRec, cfRec)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -41,14 +41,18 @@ recs, err := provider.GetRecords(ctx, zone)
// create records (AppendRecords is similar) // create records (AppendRecords is similar)
newRecs, err := provider.SetRecords(ctx, zone, []libdns.Record{ newRecs, err := provider.SetRecords(ctx, zone, []libdns.Record{
{
Type: "A", Type: "A",
Name: "sub", Name: "sub",
Value: "1.2.3.4", Value: "1.2.3.4",
},
}) })
// delete records (this example uses provider-assigned ID) // delete records (this example uses provider-assigned ID)
deletedRecs, err := provider.DeleteRecords(ctx, zone, []libdns.Record{ deletedRecs, err := provider.DeleteRecords(ctx, zone, []libdns.Record{
{
ID: "foobar", ID: "foobar",
},
}) })
// no matter which provider you use, the code stays the same! // no matter which provider you use, the code stays the same!
@ -56,11 +60,11 @@ deletedRecs, err := provider.DeleteRecords(ctx, zone, []libdns.Record{
``` ```
## Implementing new providers ## Implementing new provider packages
Providers are 100% written and maintained by the community! We all maintain just the packages for providers we use. Provider packages are 100% written and maintained by the community! Collectively, we all maintain the packages for providers we individually use.
**[Instructions for adding new providers](https://github.com/libdns/libdns/wiki/Implementing-providers)** are on this repo's wiki. Please feel free to contribute. **[Instructions for adding new libdns packages](https://github.com/libdns/libdns/wiki/Implementing-a-libdns-package)** are on this repo's wiki. Please feel free to contribute yours!
## Similar projects ## Similar projects

View File

@ -10,15 +10,18 @@
// that input records conform to this standard, while also ensuring that // that input records conform to this standard, while also ensuring that
// output records do; adjustments to record names may need to be made before // output records do; adjustments to record names may need to be made before
// or after provider API calls, for example, to maintain consistency with // or after provider API calls, for example, to maintain consistency with
// all other libdns provider implementations. Helper functions are available // all other libdns packages. Helper functions are available in this package
// in this package to convert between relative and absolute names. // to convert between relative and absolute names.
// //
// Although zone names are a required input, libdns does not coerce any // Although zone names are a required input, libdns does not coerce any
// particular representation of DNS zones; only records. Since zone name and // particular representation of DNS zones; only records. Since zone name and
// records are separate inputs in libdns interfaces, it is up to the caller // records are separate inputs in libdns interfaces, it is up to the caller
// to pair a zone's name with its records in a way that works for them. // to pair a zone's name with its records in a way that works for them.
// //
// All interface implementations must be safe for concurrent/parallel use. // All interface implementations must be safe for concurrent/parallel use,
// meaning 1) no data races, and 2) simultaneous method calls must result
// in either both their expected outcomes or an error.
//
// For example, if AppendRecords() is called at the same time and two API // For example, if AppendRecords() is called at the same time and two API
// requests are made to the provider at the same time, the result of both // requests are made to the provider at the same time, the result of both
// requests must be visible after they both complete; if the provider does // requests must be visible after they both complete; if the provider does
@ -32,6 +35,8 @@ package libdns
import ( import (
"context" "context"
"fmt"
"strconv"
"strings" "strings"
"time" "time"
) )
@ -89,7 +94,23 @@ type RecordDeleter interface {
DeleteRecords(ctx context.Context, zone string, recs []Record) ([]Record, error) DeleteRecords(ctx context.Context, zone string, recs []Record) ([]Record, error)
} }
// ZoneLister can list available DNS zones.
type ZoneLister interface {
// ListZones returns the list of available DNS zones for use by
// other libdns methods.
//
// Implementations must honor context cancellation and be safe for
// concurrent use.
ListZones(ctx context.Context) ([]Zone, error)
}
// Record is a generalized representation of a DNS record. // Record is a generalized representation of a DNS record.
//
// The values of this struct should be free of zone-file-specific syntax,
// except if this struct's fields do not sufficiently represent all the
// fields of a certain record type; in that case, the remaining data for
// which there are not specific fields should be stored in the Value as
// it appears in the zone file.
type Record struct { type Record struct {
// provider-specific metadata // provider-specific metadata
ID string ID string
@ -101,7 +122,76 @@ type Record struct {
TTL time.Duration TTL time.Duration
// type-dependent record fields // type-dependent record fields
Priority int // used by MX, SRV, and URI records Priority uint // HTTPS, MX, SRV, and URI records
Weight uint // SRV and URI records
}
// Zone is a generalized representation of a DNS zone.
type Zone struct {
Name string
}
// ToSRV parses the record into a SRV struct with fully-parsed, literal values.
//
// EXPERIMENTAL; subject to change or removal.
func (r Record) ToSRV() (SRV, error) {
if r.Type != "SRV" {
return SRV{}, fmt.Errorf("record type not SRV: %s", r.Type)
}
fields := strings.Fields(r.Value)
if len(fields) != 2 {
return SRV{}, fmt.Errorf("malformed SRV value; expected: '<port> <target>'")
}
port, err := strconv.Atoi(fields[0])
if err != nil {
return SRV{}, fmt.Errorf("invalid port %s: %v", fields[0], err)
}
if port < 0 {
return SRV{}, fmt.Errorf("port cannot be < 0: %d", port)
}
parts := strings.SplitN(r.Name, ".", 3)
if len(parts) < 3 {
return SRV{}, fmt.Errorf("name %v does not contain enough fields; expected format: '_service._proto.name'", r.Name)
}
return SRV{
Service: strings.TrimPrefix(parts[0], "_"),
Proto: strings.TrimPrefix(parts[1], "_"),
Name: parts[2],
Priority: r.Priority,
Weight: r.Weight,
Port: uint(port),
Target: fields[1],
}, nil
}
// SRV contains all the parsed data of an SRV record.
//
// EXPERIMENTAL; subject to change or removal.
type SRV struct {
Service string // no leading "_"
Proto string // no leading "_"
Name string
Priority uint
Weight uint
Port uint
Target string
}
// ToRecord converts the parsed SRV data to a Record struct.
//
// EXPERIMENTAL; subject to change or removal.
func (s SRV) ToRecord() Record {
return Record{
Type: "SRV",
Name: fmt.Sprintf("_%s._%s.%s", s.Service, s.Proto, s.Name),
Priority: s.Priority,
Weight: s.Weight,
Value: fmt.Sprintf("%d %s", s.Port, s.Target),
}
} }
// RelativeName makes fqdn relative to zone. For example, for a FQDN of // RelativeName makes fqdn relative to zone. For example, for a FQDN of
@ -109,7 +199,13 @@ type Record struct {
// //
// If fqdn cannot be expressed relative to zone, the input fqdn is returned. // If fqdn cannot be expressed relative to zone, the input fqdn is returned.
func RelativeName(fqdn, zone string) string { func RelativeName(fqdn, zone string) string {
return strings.TrimSuffix(strings.TrimSuffix(fqdn, zone), ".") // liberally ignore trailing dots on both fqdn and zone, because
// the relative name won't have a trailing dot anyway; I assume
// this won't be problematic...?
// (initially implemented because Cloudflare returns "fully-
// qualified" domains in their records without a trailing dot,
// but the input zone typically has a trailing dot)
return strings.TrimSuffix(strings.TrimSuffix(strings.TrimSuffix(fqdn, "."), strings.TrimSuffix(zone, ".")), ".")
} }
// AbsoluteName makes name into a fully-qualified domain name (FQDN) by // AbsoluteName makes name into a fully-qualified domain name (FQDN) by

8
vendor/modules.txt vendored
View File

@ -1,6 +1,6 @@
# github.com/libdns/cloudflare v0.1.0 # github.com/libdns/cloudflare v0.1.1
## explicit; go 1.14 ## explicit; go 1.18
github.com/libdns/cloudflare github.com/libdns/cloudflare
# github.com/libdns/libdns v0.2.1 # github.com/libdns/libdns v0.2.2
## explicit; go 1.14 ## explicit; go 1.18
github.com/libdns/libdns github.com/libdns/libdns