Compare commits
5 Commits
Author | SHA1 | Date | |
---|---|---|---|
70ff03f233 | |||
581d6613cc | |||
bc1c3842ee | |||
c2fc6d587d | |||
0a0c8474b8 |
@ -39,7 +39,7 @@ Usage of cloudflare-dns-cli:
|
||||
IP address (default 127.0.0.1)
|
||||
-m Set mx record with cname value
|
||||
-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
|
||||
Subdomain, e.g. blog. Use @ for root (default "<UNSET>")
|
||||
-x Delete records of subdomain
|
||||
|
6
go.mod
6
go.mod
@ -1,8 +1,8 @@
|
||||
module go.balki.me/cloudflare-dns-cli
|
||||
|
||||
go 1.21
|
||||
go 1.22.2
|
||||
|
||||
require (
|
||||
github.com/libdns/cloudflare v0.1.0
|
||||
github.com/libdns/libdns v0.2.1
|
||||
github.com/libdns/cloudflare v0.1.1
|
||||
github.com/libdns/libdns v0.2.2
|
||||
)
|
||||
|
9
go.sum
9
go.sum
@ -1,5 +1,4 @@
|
||||
github.com/libdns/cloudflare v0.1.0 h1:93WkJaGaiXCe353LHEP36kAWCUw0YjFqwhkBkU2/iic=
|
||||
github.com/libdns/cloudflare v0.1.0/go.mod h1:a44IP6J1YH6nvcNl1PverfJviADgXUnsozR3a7vBKN8=
|
||||
github.com/libdns/libdns v0.2.0/go.mod h1:yQCXzk1lEZmmCPa857bnk4TsOiqYasqpyOEeSObbb40=
|
||||
github.com/libdns/libdns v0.2.1 h1:Wu59T7wSHRgtA0cfxC+n1c/e+O3upJGWytknkmFEDis=
|
||||
github.com/libdns/libdns v0.2.1/go.mod h1:yQCXzk1lEZmmCPa857bnk4TsOiqYasqpyOEeSObbb40=
|
||||
github.com/libdns/cloudflare v0.1.1 h1:FVPfWwP8zZCqj268LZjmkDleXlHPlFU9KC4OJ3yn054=
|
||||
github.com/libdns/cloudflare v0.1.1/go.mod h1:9VK91idpOjg6v7/WbjkEW49bSCxj00ALesIFDhJ8PBU=
|
||||
github.com/libdns/libdns v0.2.2 h1:O6ws7bAfRPaBsgAYt8MDe2HcNBGC29hkZ9MX2eUSX3s=
|
||||
github.com/libdns/libdns v0.2.2/go.mod h1:4Bj9+5CQiNMVGf87wjX4CY3HQJypUHRuLvlsfsZqLWQ=
|
||||
|
35
main.go
35
main.go
@ -33,9 +33,10 @@ func genRecord() (libdns.Record, error) {
|
||||
case cname != "":
|
||||
if mx {
|
||||
return libdns.Record{
|
||||
Type: "MX",
|
||||
Name: sub,
|
||||
Value: cname,
|
||||
Type: "MX",
|
||||
Name: sub,
|
||||
Value: cname,
|
||||
Priority: 10,
|
||||
}, nil
|
||||
} else {
|
||||
return libdns.Record{
|
||||
@ -63,9 +64,9 @@ func genRecord() (libdns.Record, error) {
|
||||
func selectRecordsToDelete(recs []libdns.Record) []libdns.Record {
|
||||
name := func() string {
|
||||
if sub == "@" {
|
||||
return domain
|
||||
return ""
|
||||
}
|
||||
return fmt.Sprintf("%s.%s", sub, domain)
|
||||
return sub
|
||||
}()
|
||||
recs = func() (result []libdns.Record) {
|
||||
for _, r := range recs {
|
||||
@ -75,12 +76,16 @@ func selectRecordsToDelete(recs []libdns.Record) []libdns.Record {
|
||||
}
|
||||
return
|
||||
}()
|
||||
fmt.Printf("Records matching %s\n", name)
|
||||
if len(recs) == 0 {
|
||||
fmt.Printf("No Records match %s\n", name)
|
||||
return nil
|
||||
}
|
||||
fmt.Printf("Records matching %s\n\n", name)
|
||||
for i, r := range recs {
|
||||
fmt.Printf("%5d %6s %s\n", i, r.Type, r.Value)
|
||||
}
|
||||
var delRange string
|
||||
fmt.Print("Enter record indexes seperated by comma(,) to delete. Use hyphen(-) for a closed range. Also can be all or none\nIndexes: ")
|
||||
fmt.Print("\nEnter record indexes seperated by comma(,) to delete. Use hyphen(-) for a closed range. Also can be all or none\nIndexes: ")
|
||||
if _, err := fmt.Scanln(&delRange); err != nil {
|
||||
log.Panicln(err)
|
||||
}
|
||||
@ -108,7 +113,7 @@ func main() {
|
||||
flag.StringVar(&token, "a", token, "Cloudflare API Token. env var: CF_TOKEN")
|
||||
flag.TextVar(&ip, "i", ip, "IP address")
|
||||
flag.StringVar(&cname, "c", cname, "CNAME target")
|
||||
flag.StringVar(&path, "o", path, "Path to save all records as json, e.g. ./records.json")
|
||||
flag.StringVar(&path, "o", path, "Path to save all records as json, e.g. ./records.json, '-' for stdout")
|
||||
flag.BoolVar(&del, "x", del, "Delete records of subdomain")
|
||||
flag.BoolVar(&mx, "m", mx, "Set mx record with cname value")
|
||||
flag.Parse()
|
||||
@ -165,14 +170,20 @@ func main() {
|
||||
if err != nil {
|
||||
log.Panicln(err)
|
||||
}
|
||||
data, err := json.Marshal(recs)
|
||||
|
||||
data, err := json.Marshal(recs)
|
||||
if err != nil {
|
||||
log.Panicln(err)
|
||||
}
|
||||
err = os.WriteFile(path, data, 0644)
|
||||
if err != nil {
|
||||
log.Panicln(err)
|
||||
|
||||
if path == "-" {
|
||||
if _, err = os.Stdout.Write(data); err != nil {
|
||||
log.Panicln(err)
|
||||
}
|
||||
} else {
|
||||
if err = os.WriteFile(path, data, 0644); err != nil {
|
||||
log.Panicln(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -14,6 +14,14 @@ func TestParseRange(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseRangeEmpty(t *testing.T) {
|
||||
expectedLen := 0
|
||||
actual := parseRange("", 10)
|
||||
if expectedLen != len(actual) {
|
||||
t.Errorf("unexpected %#v\n", actual)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseIP(t *testing.T) {
|
||||
var ip net.IP
|
||||
if err := ip.UnmarshalText([]byte("127.0.0.1")); err != nil {
|
||||
|
6
vendor/github.com/libdns/cloudflare/client.go
generated
vendored
6
vendor/github.com/libdns/cloudflare/client.go
generated
vendored
@ -12,7 +12,11 @@ import (
|
||||
)
|
||||
|
||||
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 {
|
||||
return cfDNSRecord{}, err
|
||||
}
|
||||
|
70
vendor/github.com/libdns/cloudflare/models.go
generated
vendored
70
vendor/github.com/libdns/cloudflare/models.go
generated
vendored
@ -2,6 +2,7 @@ package cloudflare
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/libdns/libdns"
|
||||
@ -61,7 +62,8 @@ type cfDNSRecord struct {
|
||||
ZoneName string `json:"zone_name,omitempty"`
|
||||
CreatedOn time.Time `json:"created_on,omitempty"`
|
||||
ModifiedOn time.Time `json:"modified_on,omitempty"`
|
||||
Data *struct {
|
||||
Priority uint `json:"priority,omitempty"`
|
||||
Data struct {
|
||||
// LOC
|
||||
LatDegrees int `json:"lat_degrees,omitempty"`
|
||||
LatMinutes int `json:"lat_minutes,omitempty"`
|
||||
@ -80,9 +82,9 @@ type cfDNSRecord struct {
|
||||
Service string `json:"service,omitempty"`
|
||||
Proto string `json:"proto,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
Priority int `json:"priority,omitempty"`
|
||||
Weight int `json:"weight,omitempty"`
|
||||
Port int `json:"port,omitempty"`
|
||||
Priority uint `json:"priority,omitempty"`
|
||||
Weight uint `json:"weight,omitempty"`
|
||||
Port uint `json:"port,omitempty"`
|
||||
Target string `json:"target,omitempty"`
|
||||
|
||||
// DNSKEY
|
||||
@ -109,23 +111,57 @@ type cfDNSRecord struct {
|
||||
}
|
||||
|
||||
func (r cfDNSRecord) libdnsRecord(zone string) libdns.Record {
|
||||
return 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()
|
||||
}
|
||||
out := libdns.Record{
|
||||
Type: r.Type,
|
||||
Name: libdns.RelativeName(r.Name, zone),
|
||||
Value: r.Content,
|
||||
TTL: time.Duration(r.TTL) * time.Second,
|
||||
ID: r.ID,
|
||||
}
|
||||
if r.Type == "MX" {
|
||||
out.Priority = r.Priority
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func cloudflareRecord(r libdns.Record) cfDNSRecord {
|
||||
return cfDNSRecord{
|
||||
ID: r.ID,
|
||||
Type: r.Type,
|
||||
Name: r.Name,
|
||||
Content: r.Value,
|
||||
TTL: int(r.TTL.Seconds()),
|
||||
func cloudflareRecord(r libdns.Record) (cfDNSRecord, error) {
|
||||
rec := cfDNSRecord{
|
||||
ID: r.ID,
|
||||
Type: r.Type,
|
||||
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
|
||||
if r.Type == "MX" {
|
||||
rec.Priority = r.Priority
|
||||
}
|
||||
}
|
||||
return rec, nil
|
||||
}
|
||||
|
||||
// All API responses have this structure.
|
||||
@ -133,10 +169,14 @@ type cfResponse struct {
|
||||
Result json.RawMessage `json:"result,omitempty"`
|
||||
Success bool `json:"success"`
|
||||
Errors []struct {
|
||||
Code int `json:"code"`
|
||||
Message string `json:"message"`
|
||||
Code int `json:"code"`
|
||||
Message string `json:"message"`
|
||||
ErrorChain []struct {
|
||||
Code int `json:"code"`
|
||||
Message string `json:"message"`
|
||||
} `json:"error_chain,omitempty"`
|
||||
} `json:"errors,omitempty"`
|
||||
Messages []interface{} `json:"messages,omitempty"`
|
||||
Messages []any `json:"messages,omitempty"`
|
||||
ResultInfo *cfResultInfo `json:"result_info,omitempty"`
|
||||
}
|
||||
|
||||
|
11
vendor/github.com/libdns/cloudflare/provider.go
generated
vendored
11
vendor/github.com/libdns/cloudflare/provider.go
generated
vendored
@ -131,7 +131,10 @@ func (p *Provider) SetRecords(ctx context.Context, zone string, records []libdns
|
||||
|
||||
var results []libdns.Record
|
||||
for _, rec := range records {
|
||||
oldRec := cloudflareRecord(rec)
|
||||
oldRec, err := cloudflareRecord(rec)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
oldRec.ZoneID = zoneInfo.ID
|
||||
if rec.ID == "" {
|
||||
// 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
|
||||
}
|
||||
// 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 {
|
||||
return nil, err
|
||||
}
|
||||
|
18
vendor/github.com/libdns/libdns/README.md
generated
vendored
18
vendor/github.com/libdns/libdns/README.md
generated
vendored
@ -41,14 +41,18 @@ recs, err := provider.GetRecords(ctx, zone)
|
||||
|
||||
// create records (AppendRecords is similar)
|
||||
newRecs, err := provider.SetRecords(ctx, zone, []libdns.Record{
|
||||
Type: "A",
|
||||
Name: "sub",
|
||||
Value: "1.2.3.4",
|
||||
{
|
||||
Type: "A",
|
||||
Name: "sub",
|
||||
Value: "1.2.3.4",
|
||||
},
|
||||
})
|
||||
|
||||
// delete records (this example uses provider-assigned ID)
|
||||
deletedRecs, err := provider.DeleteRecords(ctx, zone, []libdns.Record{
|
||||
ID: "foobar",
|
||||
{
|
||||
ID: "foobar",
|
||||
},
|
||||
})
|
||||
|
||||
// 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
|
||||
|
106
vendor/github.com/libdns/libdns/libdns.go
generated
vendored
106
vendor/github.com/libdns/libdns/libdns.go
generated
vendored
@ -10,15 +10,18 @@
|
||||
// that input records conform to this standard, while also ensuring that
|
||||
// output records do; adjustments to record names may need to be made before
|
||||
// or after provider API calls, for example, to maintain consistency with
|
||||
// all other libdns provider implementations. Helper functions are available
|
||||
// in this package to convert between relative and absolute names.
|
||||
// all other libdns packages. Helper functions are available in this package
|
||||
// to convert between relative and absolute names.
|
||||
//
|
||||
// Although zone names are a required input, libdns does not coerce any
|
||||
// particular representation of DNS zones; only records. Since zone name and
|
||||
// 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.
|
||||
//
|
||||
// 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
|
||||
// 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
|
||||
@ -32,6 +35,8 @@ package libdns
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
@ -89,7 +94,23 @@ type RecordDeleter interface {
|
||||
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.
|
||||
//
|
||||
// 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 {
|
||||
// provider-specific metadata
|
||||
ID string
|
||||
@ -101,7 +122,76 @@ type Record struct {
|
||||
TTL time.Duration
|
||||
|
||||
// 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
|
||||
@ -109,7 +199,13 @@ type Record struct {
|
||||
//
|
||||
// If fqdn cannot be expressed relative to zone, the input fqdn is returned.
|
||||
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
|
||||
|
8
vendor/modules.txt
vendored
8
vendor/modules.txt
vendored
@ -1,6 +1,6 @@
|
||||
# github.com/libdns/cloudflare v0.1.0
|
||||
## explicit; go 1.14
|
||||
# github.com/libdns/cloudflare v0.1.1
|
||||
## explicit; go 1.18
|
||||
github.com/libdns/cloudflare
|
||||
# github.com/libdns/libdns v0.2.1
|
||||
## explicit; go 1.14
|
||||
# github.com/libdns/libdns v0.2.2
|
||||
## explicit; go 1.18
|
||||
github.com/libdns/libdns
|
||||
|
Loading…
Reference in New Issue
Block a user